001/*
002 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved.
003 * 
004 * http://www.izforge.com/izpack/
005 * http://developer.berlios.de/projects/izpack/
006 * 
007 * Copyright 2005 Klaus Bartz
008 *
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *     
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022package com.coi.tools.os.win;
023
024import java.util.ArrayList;
025import java.util.Iterator;
026import java.util.List;
027
028import com.izforge.izpack.util.Debug;
029
030/**
031 * System dependent helper for MS Windows registry handling. This class is only vaild on Windows. It
032 * declares naitve methods which are implemented in COIOSHelper.dll
033 * 
034 * @author Klaus Bartz
035 */
036public class RegistryImpl
037{
038
039    /*
040     * Registry root values, extracted from winreg.h
041     */
042    public static final int HKEY_CLASSES_ROOT = 0x80000000;
043
044    public static final int HKEY_CURRENT_USER = 0x80000001;
045
046    public static final int HKEY_LOCAL_MACHINE = 0x80000002;
047
048    public static final int HKEY_USERS = 0x80000003;
049
050    public static final int HKEY_PERFORMANCE_DATA = 0x80000004;
051
052    public static final int HKEY_CURRENT_CONFIG = 0x80000005;
053
054    public static final int HKEY_DYN_DATA = 0x80000006;
055
056    private static final String DEFAULT_PLACEHOLDER = "__#$&DEFAULT_PLACEHODER_VALUE#$?";
057
058    private int currentRoot = HKEY_CURRENT_USER;
059
060    private List logging = new ArrayList();
061
062    private boolean doLogging = false;
063
064    /**
065     * Creates a new empty RegistryImpl object.
066     */
067    public RegistryImpl()
068    {
069        super();
070    }
071
072    /**
073     * Returns current root.
074     * 
075     * @return current root
076     */
077    public int getRoot()
078    {
079        return currentRoot;
080    }
081
082    /**
083     * Sets the root id to the given value.
084     * 
085     * @param i root id to be set
086     */
087    public void setRoot(int i)
088    {
089        currentRoot = i;
090    }
091
092    /**
093     * Returns the value of the given value name as RegDataContainer.
094     * 
095     * @param key key of the registry entry
096     * @param value value name of the registry entry
097     * @return the value of the given value name as RegDataContainer
098     * @throws NativeLibException
099     */
100    public RegDataContainer getValue(String key, String value) throws NativeLibException
101    {
102        return (getValue(currentRoot, key, value));
103    }
104
105    /**
106     * Returns the value of the given value name as Object. The real type depends to the type of the
107     * value.
108     * 
109     * @param key key of the registry entry
110     * @param value value name of the registry entry
111     * @return the value of the given value name as RegDataContainer
112     * @throws NativeLibException
113     */
114    public Object getValueAsObject(String key, String value) throws NativeLibException
115    {
116        return (getValue(currentRoot, key, value).getDataAsObject());
117    }
118
119    /**
120     * Returns all sub keys under the given key which is under the current root.
121     * 
122     * @param key key for which the sub keys should be detected
123     * @return all sub keys under the given key which is under the current root
124     * @throws NativeLibException
125     */
126    public String[] getSubkeys(String key) throws NativeLibException
127    {
128        return (getSubkeyNames(currentRoot, key));
129    }
130
131    /**
132     * Returns all value names under the given key which is under the current root.
133     * 
134     * @param key key for which the values should be detected
135     * @return all value names under the given key which is under the current root
136     * @throws NativeLibException
137     */
138    public String[] getValueNames(String key) throws NativeLibException
139    {
140        return (getValueNames(currentRoot, key));
141    }
142
143    /**
144     * Creates the given key under the current root.
145     * 
146     * @param key key to be created
147     * @throws NativeLibException
148     */
149    public void createKey(String key) throws NativeLibException
150    {
151        createKey(currentRoot, key);
152    }
153
154    /**
155     * Creates the given key under the given root.
156     * 
157     * @param key key to be created
158     * @throws NativeLibException
159     */
160    public void createKey(int root, String key) throws NativeLibException
161    {
162        int pathEnd = key.lastIndexOf('\\');
163        if( pathEnd < 0 )
164        {
165            throw new NativeLibException("Keys directly under the root are not allowed!");
166        }
167        String subkey = key.substring(0, pathEnd);
168        if (!exist(root, subkey))
169        { // Create missing sub keys
170            createKey(root, subkey);
171        }
172        // Create key
173        createKeyN(root, key);
174        RegistryLogItem rli = new RegistryLogItem(RegistryLogItem.CREATED_KEY, root, key, null,
175                null, null);
176        log(rli);
177
178    }
179
180    /**
181     * Returns whether the given key under the current root exist or not.
182     * 
183     * @param key key to be tested
184     * @return true if thekey exist, else false
185     * @throws NativeLibException
186     */
187    public boolean keyExist(String key) throws NativeLibException
188    {
189        return (keyExist(currentRoot, key));
190    }
191
192    /**
193     * Returns whether the given key under the given root exist or not.
194     * 
195     * @param root to be used
196     * @param key key to be tested
197     * @return true if thekey exist, else false
198     * @throws NativeLibException
199     */
200    public boolean keyExist(int root, String key) throws NativeLibException
201    {
202        try
203        {
204            return (exist(root, key));
205        }
206        catch (NativeLibException ne)
207        {
208            String em = ne.getLibMessage();
209            if (em.equals("functionFailed.RegOpenKeyEx")) { return (false); }
210            throw (ne);
211        }
212    }
213
214    /**
215     * Returns whether the given value exist under the current root or not.
216     * 
217     * @param key key of the value for which should be tested
218     * @param value value to be tested
219     * @return true if the value exist, else false
220     * @throws NativeLibException
221     */
222    public boolean valueExist(String key, String value) throws NativeLibException
223    {
224        try
225        {
226            this.getValue(currentRoot, key, value);
227        }
228        catch (NativeLibException ne)
229        {
230            String em = ne.getLibMessage();
231            if (em.equals("functionFailed.RegOpenKeyEx")) { return (false); }
232            throw (ne);
233        }
234        return (true);
235    }
236
237    /**
238     * Sets the given contents to the given registry value. If a sub key or the registry value does
239     * not exist, it will be created. REG_SZ is used as registry value type.
240     * 
241     * @param key the registry key which should be used or created
242     * @param value the registry value into which the contents should be set
243     * @param contents the contents for the value
244     * @throws NativeLibException
245     */
246    public void setValue(String key, String value, String contents) throws NativeLibException
247    {
248
249        setValue(currentRoot, key, value, new RegDataContainer(contents));
250    }
251
252    /**
253     * Sets the given contents to the given registry value. If a sub key or the registry value does
254     * not exist, it will be created. REG_MULTI_SZ is used as registry value type.
255     * 
256     * @param key the registry key which should be used or created
257     * @param value the registry value into which the contents should be set
258     * @param contents the contents for the value
259     * @throws NativeLibException
260     */
261    public void setValue(String key, String value, String[] contents) throws NativeLibException
262    {
263
264        setValue(currentRoot, key, value, new RegDataContainer(contents));
265    }
266
267    /**
268     * Sets the given contents to the given registry value. If a sub key or the registry value does
269     * not exist, it will be created. REG_BINARY is used as registry value type.
270     * 
271     * @param key the registry key which should be used or created
272     * @param value the registry value into which the contents should be set
273     * @param contents the contents for the value
274     * @throws NativeLibException
275     */
276    public void setValue(String key, String value, byte[] contents) throws NativeLibException
277    {
278
279        setValue(currentRoot, key, value, new RegDataContainer(contents));
280    }
281
282    /**
283     * Sets the given contents to the given registry value. If a sub key or the registry value does
284     * not exist, it will be created. REG_DWORD is used as registry value type.
285     * 
286     * @param key the registry key which should be used or created
287     * @param value the registry value into which the contents should be set
288     * @param contents the contents for the value
289     * @throws NativeLibException
290     */
291    public void setValue(String key, String value, long contents) throws NativeLibException
292    {
293        setValue(currentRoot, key, value, new RegDataContainer(contents));
294    }
295
296    /**
297     * Sets the given contents to the given registry value under current root. If a sub key or the
298     * registry value does not exist, it will be created. The used registry value type will be
299     * determined by the type of the RegDataContainer.
300     * 
301     * @param key the registry key which should be used or created
302     * @param value the registry value into which the contents should be set
303     * @param contents the contents for the value
304     * @throws NativeLibException
305     */
306    public void setValue(String key, String value, RegDataContainer contents)
307            throws NativeLibException
308    {
309        setValue(currentRoot, key, value, contents);
310    }
311
312    /**
313     * Sets the given contents to the given registry value. If a sub key or the registry value does
314     * not exist, it will be created. The used registry value type will be determined by the type of
315     * the RegDataContainer.
316     * 
317     * @param root id for the root of the key
318     * @param key the registry key which should be used or created
319     * @param value the registry value into which the contents should be set
320     * @param contents the contents for the value
321     * @throws NativeLibException
322     */
323    public void setValue(int root, String key, String value, RegDataContainer contents)
324            throws NativeLibException
325    {
326        RegDataContainer oldContents = null;
327        String localValue = value;
328        // May be someone do not like backslashes ...
329        key = key.replace('/', '\\');
330
331        synchronized (logging)
332        {
333            try
334            {
335                oldContents = getValue(currentRoot, key, value);
336            }
337            catch (NativeLibException ne)
338            {
339                String em = ne.getLibMessage();
340                if (em.equals("functionFailed.RegOpenKeyEx")
341                        || em.equals("functionFailed.RegQueryValueEx"))
342                {
343                    setValueR(root, key, value, contents);
344                    return;
345                }
346                throw (ne);
347            }
348            setValueN(root, key, value, contents);
349            // Add value changing to log list
350            if (value.length() == 0) // The default value ...
351                localValue = DEFAULT_PLACEHOLDER; // Rewind will fail if last
352            // token is
353            // empty.
354
355            RegistryLogItem rli = new RegistryLogItem(RegistryLogItem.CHANGED_VALUE, root, key,
356                    localValue, contents, oldContents);
357            log(rli);
358        }
359        return;
360    }
361
362    /**
363     * Deletes a key under the current root if exist and it is empty, else throw an exception.
364     * 
365     * @param key key to be deleted
366     * @throws NativeLibException
367     */
368    public void deleteKey(String key) throws NativeLibException
369    {
370        deleteKeyL(currentRoot, key);
371    }
372    /**
373     * Deletes a key under the current root if it is empty, else do nothing.
374     * 
375     * @param key key to be deleted
376     * @throws NativeLibException
377     */
378    public void deleteKeyIfEmpty(String key) throws NativeLibException
379    {
380        deleteKeyIfEmpty(currentRoot, key);
381    }
382
383    /**
384     * Deletes a key if it is empty, else do nothing.
385     * 
386     * @param root id for the root of the key
387     * @param key key to be deleted
388     * @throws NativeLibException
389     */
390    public void deleteKeyIfEmpty(int root, String key) throws NativeLibException
391    {
392        if (keyExist(root, key) && isKeyEmpty(root, key) ) deleteKeyL(root, key);
393
394    }
395
396    /**
397     * Deletes a value.
398     * 
399     * @param key key of the value which should be deleted
400     * @param value value name to be deleted
401     * @throws NativeLibException
402     */
403    public void deleteValue(String key, String value) throws NativeLibException
404    {
405        deleteValueL(currentRoot, key, value);
406    }
407
408    /**
409     * Deletes a key with logging.
410     * 
411     * @param root id for the root of the key
412     * @param key key to be deleted
413     * @throws NativeLibException
414     */
415    private void deleteKeyL(int root, String key) throws NativeLibException
416    {
417        RegistryLogItem rli = new RegistryLogItem(RegistryLogItem.REMOVED_KEY, root, key, null,
418                null, null);
419        log(rli);
420        deleteKeyN(root, key);
421    }
422
423    /**
424     * Deletes a value with logging.
425     * 
426     * @param root id for the root of the key
427     * @param key key of the value which should be deleted
428     * @param value value name to be deleted
429     * @throws NativeLibException
430     */
431    private void deleteValueL(int root, String key, String value) throws NativeLibException
432    {
433        RegDataContainer oldContents = getValue(currentRoot, key, value);
434        RegistryLogItem rli = new RegistryLogItem(RegistryLogItem.REMOVED_VALUE, root, key, value,
435                null, oldContents);
436        log(rli);
437        deleteValueN(currentRoot, key, value);
438    }
439
440    /**
441     * Rewinds all logged actions.
442     * 
443     * @throws IllegalArgumentException
444     * @throws NativeLibException
445     */
446    public void rewind() throws IllegalArgumentException, NativeLibException
447    {
448        synchronized (logging)
449        {
450            Iterator iter = logging.iterator();
451            suspendLogging();
452
453            while (iter.hasNext())
454            {
455                RegistryLogItem rli = (RegistryLogItem) iter.next();
456                switch (rli.getType())
457                {
458                case RegistryLogItem.CREATED_KEY:
459                    deleteKeyIfEmpty(rli.getRoot(), rli.getKey());
460                    break;
461                case RegistryLogItem.REMOVED_KEY:
462                    createKeyN(rli.getRoot(), rli.getKey());
463                    break;
464                case RegistryLogItem.CREATED_VALUE:
465                    RegDataContainer currentContents = null;
466                    // Delete value only if reg entry exists and equal to the
467                    // stored
468                    // value.
469                    try
470                    {
471                        currentContents = getValue(rli.getRoot(), rli.getKey(), rli.getValueName());
472                    }
473                    catch (NativeLibException nle)
474                    {
475                        break;
476                    }
477                    if (currentContents.equals(rli.getNewValue()))
478                    {
479                        Debug.error("delete it ");
480                        deleteValueN(rli.getRoot(), rli.getKey(), rli.getValueName());
481                    }
482                    // TODO: what todo if value has changed?
483                    break;
484                case RegistryLogItem.REMOVED_VALUE:
485                    // Set old value only if reg entry not exists.
486                    try
487                    {
488                        getValue(rli.getRoot(), rli.getKey(), rli.getValueName());
489                    }
490                    catch (NativeLibException nle)
491                    {
492                        setValueN(rli.getRoot(), rli.getKey(), rli.getValueName(), rli
493                                .getOldValue());
494                    }
495                    break;
496                case RegistryLogItem.CHANGED_VALUE:
497                    // Change to old value only if reg entry exists and equal to
498                    // the
499                    // stored value.
500                    try
501                    {
502                        currentContents = getValue(rli.getRoot(), rli.getKey(), rli.getValueName());
503                    }
504                    catch (NativeLibException nle)
505                    {
506                        break;
507                    }
508                    if (currentContents.equals(rli.getNewValue()))
509                    {
510                        setValueN(rli.getRoot(), rli.getKey(), rli.getValueName(), rli
511                                .getOldValue());
512                    }
513                    break;
514                }
515            }
516        }
517
518    }
519
520    /**
521     * Sets the given contents to the given registry value. If a sub key or the registry value does
522     * not exist, it will be created. The used registry value type will be determined by the type of
523     * the RegDataContainer.
524     * 
525     * @param root id for the root of the key
526     * @param key the registry key which should be used or created
527     * @param value the registry value into which the contents should be set
528     * @param contents the contents for the value
529     * @throws NativeLibException
530     */
531    private void setValueR(int root, String key, String value, RegDataContainer contents)
532            throws NativeLibException
533    {
534        String localValue = value;
535        if (!exist(root, key))
536        { // Create missing sub keys
537            createKey(root, key);
538        }
539        // Create value
540        setValueN(root, key, value, contents);
541        // Add value creation to log list
542        if (value.length() == 0) // The default value ...
543            localValue = DEFAULT_PLACEHOLDER; // Rewind will fail if last token
544        // is
545        // empty.
546        StringBuffer sb = new StringBuffer();
547        sb.append("SetValue;").append(Integer.toString(root)).append(";").append(key).append(";")
548                .append(localValue);
549        RegistryLogItem rli = new RegistryLogItem(RegistryLogItem.CREATED_VALUE, root, key,
550                localValue, contents, null);
551        log(rli);
552    }
553
554    private native boolean exist(int root, String key) throws NativeLibException;
555
556    private native void createKeyN(int root, String key) throws NativeLibException;
557
558    private native void setValueN(int root, String key, String value, RegDataContainer contents)
559            throws NativeLibException;
560
561    private native int getValueType(int root, String key, String value) throws NativeLibException;
562
563    private native RegDataContainer getValue(int root, String key, String value)
564            throws NativeLibException;
565
566    private native void deleteValueN(int root, String key, String value) throws NativeLibException;
567
568    private native void deleteKeyN(int root, String key) throws NativeLibException;
569
570    private native boolean isKeyEmpty(int root, String key) throws NativeLibException;
571
572    private native int getSubkeyCount(int root, String key) throws NativeLibException;
573
574    private native int getValueCount(int root, String key) throws NativeLibException;
575
576    private native String getSubkeyName(int root, String key, int index) throws NativeLibException;
577
578    private native String getValueName(int root, String key, int index) throws NativeLibException;
579
580    private native String[] getSubkeyNames(int root, String key) throws NativeLibException;
581
582    private native String[] getValueNames(int root, String key) throws NativeLibException;
583
584    /**
585     * Creates a new (empty) logging list and activates logging.
586     */
587    public void resetLogging()
588    {
589        logging = new ArrayList();
590        activateLogging();
591    }
592
593    /**
594     * Suspends logging.
595     */
596    public void suspendLogging()
597    {
598        doLogging = false;
599    }
600
601    /**
602     * Activates logging.
603     */
604    public void activateLogging()
605    {
606        doLogging = true;
607    }
608
609    /**
610     * Returns a copy of the colected logging informations.
611     */
612    public List getLoggingInfo()
613    {
614        ArrayList retval = new ArrayList(logging.size());
615        Iterator iter = logging.iterator();
616        while (iter.hasNext())
617            try
618            {
619                retval.add(((RegistryLogItem) iter.next()).clone());
620            }
621            catch (CloneNotSupportedException e)
622            { // Should never be...
623                e.printStackTrace();
624            }
625        return (retval);
626    }
627
628    /**
629     * Copies the contents of the given list of RegistryLogItems to a newly created internal logging
630     * list.
631     */
632    public void setLoggingInfo(List info)
633    {
634        resetLogging();
635        addLoggingInfo(info);
636    }
637
638    /**
639     * Adds copies of the contents of the given list of RegistryLogItems to the existent internal
640     * logging list.
641     */
642    public void addLoggingInfo(List info)
643    {
644        Iterator iter = info.iterator();
645        while (iter.hasNext())
646            try
647            {
648                logging.add(((RegistryLogItem) iter.next()).clone());
649            }
650            catch (CloneNotSupportedException e)
651            { // Should never be
652                e.printStackTrace();
653            }
654
655    }
656
657    /**
658     * Adds the given item to the beginning of the logging list if logging is enabled, else do
659     * nothing.
660     * 
661     * @param item
662     */
663    private void log(RegistryLogItem item)
664    {
665        if (doLogging && logging != null) logging.add(0, item);
666    }
667
668    /**
669     * Adds the given item to the end of the logging list if logging is enabled, else do nothing.
670     * 
671     * @param item
672     */
673    private void logAtEnd(RegistryLogItem item)
674    {
675        if (doLogging && logging != null) logging.add(item);
676    }
677
678}