1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.lib.conf;
20
21 import java.awt;
22 import java.beans.BeanDescriptor;
23 import java.beans.BeanInfo;
24 import java.beans.EventSetDescriptor;
25 import java.beans.IntrospectionException;
26 import java.beans.Introspector;
27 import java.beans.MethodDescriptor;
28 import java.beans.PropertyChangeListener;
29 import java.beans.PropertyChangeSupport;
30 import java.beans.PropertyDescriptor;
31 import java.io.Externalizable;
32 import java.io.File;
33 import java.io.IOException;
34 import java.io.ObjectInput;
35 import java.io.ObjectOutput;
36 import java.io.PrintWriter;
37 import java.io.Serializable;
38 import java.io.StringWriter;
39 import java.lang.reflect.Constructor;
40 import java.lang.reflect.InvocationTargetException;
41 import java.lang.reflect.Method;
42 import java.security.AccessController;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.HashMap;
48 import java.util.Iterator;
49 import java.util.LinkedHashMap;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.MissingResourceException;
53 import java.util.Properties;
54 import java.util.TreeSet;
55
56 import org.apache.commons.lang.StringUtils;
57 import org.apache.openjpa.lib.log.Log;
58 import org.apache.openjpa.lib.log.LogFactory;
59 import org.apache.openjpa.lib.log.LogFactoryImpl;
60 import org.apache.openjpa.lib.log.NoneLogFactory;
61 import org.apache.openjpa.lib.util.Closeable;
62 import org.apache.openjpa.lib.util.J2DoPrivHelper;
63 import org.apache.openjpa.lib.util.Localizer;
64 import org.apache.openjpa.lib.util.MultiClassLoader;
65 import org.apache.openjpa.lib.util.ParseException;
66 import org.apache.openjpa.lib.util.Services;
67 import org.apache.openjpa.lib.util.StringDistance;
68 import serp.util.Strings;
69
70 /**
71 * Default implementation of the {@link Configuration} interface.
72 * Subclasses can choose to obtain configuration
73 * information from JNDI, Properties, a Bean-builder, etc. This class
74 * provides base configuration functionality, including serialization,
75 * the <code>equals</code> and <code>hashCode</code> contracts, and default
76 * property loading.
77 * Property descriptors for {@link Value} instances are constructed from
78 * the {@link Localizer} for the package of the configuration class. The
79 * following localized strings will be used for describing a value, where
80 * <em>name</em> is the last token of the value's property string:
81 * <ul>
82 * <li><em>name</em>-name: The name that will be displayed for the
83 * option in a user interface; required.</li>
84 * <li><em>name</em>-desc: A brief description of the option; required.</li>
85 * <li><em>name</em>-type: The type or category name for this option;
86 * required.</li>
87 * <li><em>name</em>-expert: True if this is an expert option, false
88 * otherwise; defaults to false.</li>
89 * <li><em>name</em>-values: Set of expected or common values, excluding
90 * alias keys; optional.</li>
91 * <li><em>name</em>-interface: The class name of an interface whose
92 * discoverable implementations should be included in the set of expected
93 * or common values; optional.</li>
94 * <li><em>name</em>-cat: The hierarchical category for the property
95 * name, separated by ".".
96 * <li><em>name</em>-displayorder: The order in which the property should
97 * be displayer.</li>
98 * </ul>
99 *
100 * @author Abe White
101 */
102 public class ConfigurationImpl
103 implements Configuration, Externalizable, ValueListener {
104
105 private static final String SEP = J2DoPrivHelper.getLineSeparator();
106
107 private static final Localizer _loc = Localizer.forPackage
108 (ConfigurationImpl.class);
109
110 public ObjectValue logFactoryPlugin;
111 public StringValue id;
112
113 private String _product = null;
114 private int _readOnlyState = INIT_STATE_LIQUID;
115 private Map _props = null;
116 private boolean _globals = false;
117 private String _auto = null;
118 private final List _vals = new ArrayList();
119
120 // property listener helper
121 private PropertyChangeSupport _changeSupport = null;
122
123 // cache descriptors
124 private PropertyDescriptor[] _pds = null;
125 private MethodDescriptor[] _mds = null;
126
127 /**
128 * Default constructor. Attempts to load default properties through
129 * system's configured {@link ProductDerivation}s.
130 */
131 public ConfigurationImpl() {
132 this(true);
133 }
134
135 /**
136 * Constructor.
137 *
138 * @param loadGlobals whether to attempt to load the global properties
139 */
140 public ConfigurationImpl(boolean loadGlobals) {
141 setProductName("openjpa");
142
143 logFactoryPlugin = addPlugin("Log", true);
144 String[] aliases = new String[]{
145 "true", LogFactoryImpl.class.getName(),
146 "openjpa", LogFactoryImpl.class.getName(),
147 "commons", "org.apache.openjpa.lib.log.CommonsLogFactory",
148 "log4j", "org.apache.openjpa.lib.log.Log4JLogFactory",
149 "none", NoneLogFactory.class.getName(),
150 "false", NoneLogFactory.class.getName(),
151 };
152 logFactoryPlugin.setAliases(aliases);
153 logFactoryPlugin.setDefault(aliases[0]);
154 logFactoryPlugin.setString(aliases[0]);
155 logFactoryPlugin.setInstantiatingGetter("getLogFactory");
156
157 id = addString("Id");
158
159 if (loadGlobals)
160 loadGlobals();
161 }
162
163 /**
164 * Automatically load global values from the system's
165 * {@link ProductDerivation}s, and from System properties.
166 */
167 public boolean loadGlobals() {
168 MultiClassLoader loader = (MultiClassLoader) AccessController
169 .doPrivileged(J2DoPrivHelper.newMultiClassLoaderAction());
170 loader.addClassLoader((ClassLoader) AccessController.doPrivileged(
171 J2DoPrivHelper.getContextClassLoaderAction()));
172 loader.addClassLoader(getClass().getClassLoader());
173 ConfigurationProvider provider = ProductDerivations.loadGlobals(loader);
174 if (provider != null)
175 provider.setInto(this);
176
177 // let system properties override other globals
178 try {
179 fromProperties(new HashMap(
180 (Properties) AccessController.doPrivileged(
181 J2DoPrivHelper.getPropertiesAction())));
182 } catch (SecurityException se) {
183 // security manager might disallow
184 }
185
186 _globals = true;
187 if (provider == null) {
188 Log log = getConfigurationLog();
189 if (log.isTraceEnabled())
190 log.trace(_loc.get("no-default-providers"));
191 return false;
192 }
193 return true;
194 }
195
196 public String getProductName() {
197 return _product;
198 }
199
200 public void setProductName(String name) {
201 _product = name;
202 }
203
204 public LogFactory getLogFactory() {
205 if (logFactoryPlugin.get() == null)
206 logFactoryPlugin.instantiate(LogFactory.class, this);
207 return (LogFactory) logFactoryPlugin.get();
208 }
209
210 public void setLogFactory(LogFactory logFactory) {
211 logFactoryPlugin.set(logFactory);
212 }
213
214 public String getLog() {
215 return logFactoryPlugin.getString();
216 }
217
218 public void setLog(String log) {
219 logFactoryPlugin.setString(log);
220 }
221
222 public Log getLog(String category) {
223 return getLogFactory().getLog(category);
224 }
225
226 public String getId() {
227 return id.get();
228 }
229
230 public void setId(String id) {
231 this.id.set(id);
232 }
233
234 /**
235 * Returns the logging channel <code>openjpa.Runtime</code> by default.
236 */
237 public Log getConfigurationLog() {
238 return getLog("openjpa.Runtime");
239 }
240
241 public Value[] getValues() {
242 return (Value[]) _vals.toArray(new Value[_vals.size()]);
243 }
244
245 public Value getValue(String property) {
246 if (property == null)
247 return null;
248
249 // search backwards so that custom values added after construction
250 // are found quickly, since this will be the std way of accessing them
251 Value val;
252 for (int i = _vals.size() - 1; i >= 0; i--) {
253 val = (Value) _vals.get(i);
254 if (val.getProperty().equals(property))
255 return val;
256 }
257 return null;
258 }
259
260 public void setReadOnly(int newState) {
261 if (newState >= _readOnlyState) {
262 _readOnlyState = newState;
263 }
264 }
265
266 public void instantiateAll() {
267 StringWriter errs = null;
268 PrintWriter stack = null;
269 Value val;
270 String getterName;
271 Method getter;
272 Object getterTarget;
273 for (int i = 0; i < _vals.size(); i++) {
274 val = (Value) _vals.get(i);
275 getterName = val.getInstantiatingGetter();
276 if (getterName == null)
277 continue;
278
279 getterTarget = this;
280 if (getterName.startsWith("this.")) {
281 getterName = getterName.substring("this.".length());
282 getterTarget = val;
283 }
284
285 try {
286 getter = getterTarget.getClass().getMethod(getterName,
287 (Class[]) null);
288 getter.invoke(getterTarget, (Object[]) null);
289 } catch (Throwable t) {
290 if (t instanceof InvocationTargetException)
291 t = ((InvocationTargetException) t).getTargetException();
292 if (errs == null) {
293 errs = new StringWriter();
294 stack = new PrintWriter(errs);
295 } else
296 errs.write(SEP);
297 t.printStackTrace(stack);
298 stack.flush();
299 }
300 }
301 if (errs != null)
302 throw new RuntimeException(_loc.get("get-prop-errs",
303 errs.toString()).getMessage());
304 }
305
306 public boolean isReadOnly() {
307 return _readOnlyState==INIT_STATE_FROZEN;
308 }
309
310 public void addPropertyChangeListener(PropertyChangeListener listener) {
311 if (_changeSupport == null)
312 _changeSupport = new PropertyChangeSupport(this);
313 _changeSupport.addPropertyChangeListener(listener);
314 }
315
316 public void removePropertyChangeListener(PropertyChangeListener listener) {
317 if (_changeSupport != null)
318 _changeSupport.removePropertyChangeListener(listener);
319 }
320
321 public void valueChanged(Value val) {
322 if (_changeSupport == null && _props == null)
323 return;
324
325 String newString = val.getString();
326 if (_changeSupport != null)
327 _changeSupport.firePropertyChange(val.getProperty(), null,
328 newString);
329
330 // keep cached props up to date
331 if (_props != null) {
332 if (newString == null)
333 Configurations.removeProperty(val.getProperty(), _props);
334 else if (Configurations.containsProperty(val.getProperty(), _props)
335 || val.getDefault() == null
336 || !val.getDefault().equals(newString))
337 put(_props, val, newString);
338 }
339 }
340
341 /**
342 * Closes all closeable values and plugins.
343 */
344 public final void close() {
345 ProductDerivations.beforeClose(this);
346
347 preClose();
348
349 ObjectValue val;
350 for (int i = 0; i < _vals.size(); i++) {
351 if (_vals.get(i) instanceof Closeable) {
352 try { ((Closeable) _vals.get(i)).close(); }
353 catch (Exception e) {}
354 continue;
355 }
356
357 if (!(_vals.get(i) instanceof ObjectValue))
358 continue;
359
360 val = (ObjectValue) _vals.get(i);
361 if (val.get() instanceof Closeable) {
362 try {
363 ((Closeable) val.get()).close();
364 } catch (Exception e) {
365 }
366 }
367 }
368 }
369
370 /**
371 * Invoked by final method {@link #close} after invoking the
372 * {@link ProductDerivation#beforeConfigurationClose} callbacks
373 * but before performing internal close operations.
374 *
375 * @since 0.9.7
376 */
377 protected void preClose() {
378 }
379
380 ///////////////////////////
381 // BeanInfo implementation
382 ///////////////////////////
383
384 public BeanInfo[] getAdditionalBeanInfo() {
385 return new BeanInfo[0];
386 }
387
388 public BeanDescriptor getBeanDescriptor() {
389 return new BeanDescriptor(getClass());
390 }
391
392 public int getDefaultEventIndex() {
393 return 0;
394 }
395
396 public int getDefaultPropertyIndex() {
397 return 0;
398 }
399
400 public EventSetDescriptor[] getEventSetDescriptors() {
401 return new EventSetDescriptor[0];
402 }
403
404 public Image getIcon(int kind) {
405 return null;
406 }
407
408 public synchronized MethodDescriptor[] getMethodDescriptors() {
409 if (_mds != null)
410 return _mds;
411
412 PropertyDescriptor[] pds = getPropertyDescriptors();
413 List descs = new ArrayList();
414 for (int i = 0; i < pds.length; i++) {
415 Method write = pds[i].getWriteMethod();
416 Method read = pds[i].getReadMethod();
417 if (read != null && write != null) {
418 descs.add(new MethodDescriptor(write));
419 descs.add(new MethodDescriptor(read));
420 }
421 }
422 _mds = (MethodDescriptor[]) descs.
423 toArray(new MethodDescriptor[descs.size()]);
424 return _mds;
425 }
426
427 public synchronized PropertyDescriptor[] getPropertyDescriptors() {
428 if (_pds != null)
429 return _pds;
430
431 _pds = new PropertyDescriptor[_vals.size()];
432 List failures = null;
433 Value val;
434 for (int i = 0; i < _vals.size(); i++) {
435 val = (Value) _vals.get(i);
436 try {
437 _pds[i] = getPropertyDescriptor(val);
438 } catch (MissingResourceException mre) {
439 if (failures == null)
440 failures = new ArrayList();
441 failures.add(val.getProperty());
442 } catch (IntrospectionException ie) {
443 if (failures == null)
444 failures = new ArrayList();
445 failures.add(val.getProperty());
446 }
447 }
448 if (failures != null)
449 throw new ParseException(_loc.get("invalid-property-descriptors",
450 failures));
451
452 return _pds;
453 }
454
455 /**
456 * Create a property descriptor for the given value.
457 */
458 private PropertyDescriptor getPropertyDescriptor(Value val)
459 throws IntrospectionException {
460 String prop = val.getProperty();
461 prop = prop.substring(prop.lastIndexOf('.') + 1);
462
463 // set up property descriptor
464 PropertyDescriptor pd;
465 try {
466 pd = new PropertyDescriptor(Introspector.decapitalize(prop),
467 getClass());
468 } catch (IntrospectionException ie) {
469 // if there aren't any methods for this value(i.e., if it's a
470 // dynamically-added value), then an IntrospectionException will
471 // be thrown. Try to create a PD with no read or write methods.
472 pd = new PropertyDescriptor(Introspector.decapitalize(prop),
473 (Method) null, (Method) null);
474 }
475 pd.setDisplayName(findLocalized(prop + "-name", true, val.getScope()));
476 pd.setShortDescription(findLocalized(prop + "-desc", true,
477 val.getScope()));
478 pd.setExpert("true".equals(findLocalized(prop + "-expert", false,
479 val.getScope())));
480
481 try {
482 pd.setReadMethod(getClass().getMethod("get"
483 + StringUtils.capitalize(prop), (Class[]) null));
484 pd.setWriteMethod(getClass().getMethod("set"
485 + StringUtils.capitalize(prop), new Class[]
486 { pd.getReadMethod().getReturnType() }));
487 } catch (Throwable t) {
488 // if an error occurs, it might be because the value is a
489 // dynamic property.
490 }
491
492 String type = findLocalized(prop + "-type", true, val.getScope());
493 if (type != null)
494 pd.setValue(ATTRIBUTE_TYPE, type);
495
496 String cat = findLocalized(prop + "-cat", false, val.getScope());
497 if (cat != null)
498 pd.setValue(ATTRIBUTE_CATEGORY, cat);
499
500 pd.setValue(ATTRIBUTE_XML, toXMLName(prop));
501
502 String order = findLocalized(prop + "-displayorder", false,
503 val.getScope());
504 if (order != null)
505 pd.setValue(ATTRIBUTE_ORDER, order);
506
507 // collect allowed values from alias keys, listed values, and
508 // interface implementors
509 Collection allowed = new TreeSet();
510 List aliases = Collections.EMPTY_LIST;
511 if (val.getAliases() != null) {
512 aliases = Arrays.asList(val.getAliases());
513 for (int i = 0; i < aliases.size(); i += 2)
514 allowed.add(aliases.get(i));
515 }
516 String[] vals = Strings.split(findLocalized(prop
517 + "-values", false, val.getScope()), ",", 0);
518 for (int i = 0; i < vals.length; i++)
519 if (!aliases.contains(vals[i]))
520 allowed.add(vals[i]);
521 try {
522 Class intf = Class.forName(findLocalized(prop
523 + "-interface", true, val.getScope()), false,
524 getClass().getClassLoader());
525 pd.setValue(ATTRIBUTE_INTERFACE, intf.getName());
526 String[] impls = Services.getImplementors(intf);
527 for (int i = 0; i < impls.length; i++)
528 if (!aliases.contains(impls[i]))
529 allowed.add(impls[i]);
530 } catch (Throwable t) {
531 }
532 if (!allowed.isEmpty())
533 pd.setValue(ATTRIBUTE_ALLOWED_VALUES, (String[]) allowed.toArray
534 (new String[allowed.size()]));
535
536 return pd;
537 }
538
539 /**
540 * Find the given localized string, or return null if not found.
541 */
542 private String findLocalized(String key, boolean fatal, Class scope) {
543 // find the localizer package that contains this key
544 Localizer loc = null;
545
546 // check the package that the value claims to be defined in, if
547 // available, before we start guessing.
548 if (scope != null) {
549 loc = Localizer.forPackage(scope);
550 try {
551 return loc.getFatal(key).getMessage();
552 } catch (MissingResourceException mse) {
553 }
554 }
555
556 for (Class cls = getClass(); cls != Object.class;
557 cls = cls.getSuperclass()) {
558 loc = Localizer.forPackage(cls);
559 try {
560 return loc.getFatal(key).getMessage();
561 } catch (MissingResourceException mse) {
562 }
563 }
564
565 if (fatal)
566 throw new MissingResourceException(key, getClass().getName(), key);
567 return null;
568 }
569
570 ////////////////
571 // To/from maps
572 ////////////////
573
574 public Map toProperties(boolean storeDefaults) {
575 // clone properties before making any modifications; we need to keep
576 // the internal properties instance consistent to maintain equals and
577 // hashcode contracts
578 Map clone;
579 if (_props == null)
580 clone = new HashMap();
581 else if (_props instanceof Properties)
582 clone = (Map) ((Properties) _props).clone();
583 else
584 clone = new HashMap(_props);
585
586 // if no existing properties or the properties should contain entries
587 // with default values, add values to properties
588 if (_props == null || storeDefaults) {
589 Value val;
590 String str;
591 for (int i = 0; i < _vals.size(); i++) {
592 // if key in existing properties, we already know value is up
593 // to date
594 val = (Value) _vals.get(i);
595 if (_props != null && Configurations.containsProperty
596 (val.getProperty(), _props))
597 continue;
598
599 str = val.getString();
600 if (str != null && (storeDefaults
601 || !str.equals(val.getDefault())))
602 put(clone, val, str);
603 }
604 if (_props == null)
605 _props = new HashMap(clone);
606 }
607 return clone;
608 }
609
610 public void fromProperties(Map map) {
611 if (map == null || map.isEmpty())
612 return;
613 if (isReadOnly())
614 throw new IllegalStateException(_loc.get("read-only").getMessage());
615
616 // if the only previous call was to load defaults, forget them.
617 // this way we preserve the original formatting of the user's props
618 // instead of the defaults. this is important for caching on
619 // configuration objects
620 if (_globals) {
621 _props = null;
622 _globals = false;
623 }
624
625 // copy the input to avoid mutation issues
626 if (map instanceof HashMap)
627 map = (Map) ((HashMap) map).clone();
628 else if (map instanceof Properties)
629 map = (Map) ((Properties) map).clone();
630 else
631 map = new LinkedHashMap(map);
632
633 Map remaining = new HashMap(map);
634 boolean ser = true;
635 Value val;
636 Object o;
637 for (int i = 0; i < _vals.size(); i++) {
638 val = (Value) _vals.get(i);
639 o = get(map, val, true);
640 if (o == null)
641 continue;
642
643 if (o instanceof String) {
644 if (!StringUtils.equals((String) o, val.getString()))
645 val.setString((String) o);
646 } else {
647 ser &= o instanceof Serializable;
648 val.setObject(o);
649 }
650 Configurations.removeProperty(val.getProperty(), remaining);
651 }
652
653 // convention is to point product at a resource with the
654 // <prefix>.properties System property; remove that property so we
655 // we don't warn about it
656 Configurations.removeProperty("properties", remaining);
657
658 // now warn if there are any remaining properties that there
659 // is an unhandled prop, and remove the unknown properties
660 Map.Entry entry;
661 for (Iterator itr = remaining.entrySet().iterator(); itr.hasNext();) {
662 entry = (Map.Entry) itr.next();
663 Object key = entry.getKey();
664 if (key != null) {
665 warnInvalidProperty((String) key);
666 map.remove(key);
667 }
668 }
669
670 // cache properties
671 if (_props == null && ser)
672 _props = map;
673 }
674
675 /**
676 * Adds <code>o</code> to <code>map</code> under key for <code>val</code>.
677 * Use this method instead of attempting to add the value directly because
678 * this will account for the property prefix.
679 */
680 private void put(Map map, Value val, Object o) {
681 Object key = val.getLoadKey();
682 if (key == null)
683 key = "openjpa." + val.getProperty();
684 map.put(key, o);
685 }
686
687 /**
688 * Look up the given value, testing all available prefixes.
689 */
690 private Object get(Map map, Value val, boolean setLoadKey) {
691 String key = ProductDerivations.getConfigurationKey(
692 val.getProperty(), map);
693 if (map.containsKey(key) && setLoadKey)
694 val.setLoadKey(key);
695 return map.get(key);
696 }
697
698 /**
699 * Issue a warning that the specified property is not valid.
700 */
701 private void warnInvalidProperty(String propName) {
702 if (!isInvalidProperty(propName))
703 return;
704 Log log = getConfigurationLog();
705 if (log == null || !log.isWarnEnabled())
706 return;
707
708 // try to find the closest string to the invalid property
709 // so that we can provide a hint in case of a misspelling
710 String closest = StringDistance.getClosestLevenshteinDistance
711 (propName, newPropertyList(), 15);
712
713 if (closest == null)
714 log.warn(_loc.get("invalid-property", propName));
715 else
716 log.warn(_loc.get("invalid-property-hint", propName, closest));
717 }
718
719 /**
720 * Return a comprehensive list of recognized map keys.
721 */
722 private Collection newPropertyList() {
723 String[] prefixes = ProductDerivations.getConfigurationPrefixes();
724 List l = new ArrayList(_vals.size() * prefixes.length);
725 for (int i = 0; i < _vals.size(); i++) {
726 for (int j = 0; j < prefixes.length; j++)
727 l.add(prefixes[j] + "." + ((Value) _vals.get(i)).getProperty());
728 }
729 return l;
730 }
731
732 /**
733 * Returns true if the specified property name should raise a warning
734 * if it is not found in the list of known properties.
735 */
736 protected boolean isInvalidProperty(String propName) {
737 // handle warnings for openjpa.SomeString, but not for
738 // openjpa.some.subpackage.SomeString, since it might be valid for some
739 // specific implementation of OpenJPA
740 String[] prefixes = ProductDerivations.getConfigurationPrefixes();
741 for (int i = 0; i < prefixes.length; i++) {
742 if (propName.toLowerCase().startsWith(prefixes[i])
743 && propName.length() > prefixes[i].length() + 1
744 && propName.indexOf('.', prefixes[i].length())
745 == prefixes[i].length()
746 && propName.indexOf('.', prefixes[i].length() + 1) == -1)
747 return true;
748 }
749 return false;
750 }
751
752 //////////////////////
753 // Auto-configuration
754 //////////////////////
755
756 /**
757 * This method loads the named resource as a properties file. It is
758 * useful for auto-configuration tools so users can specify a
759 * <code>properties</code> value with the name of a resource.
760 */
761 public void setProperties(String resourceName) throws IOException {
762 ProductDerivations.load(resourceName, null,
763 getClass().getClassLoader()).setInto(this);
764 _auto = resourceName;
765 }
766
767 /**
768 * This method loads the named file as a properties file. It is
769 * useful for auto-configuration tools so users can specify a
770 * <code>propertiesFile</code> value with the name of a file.
771 */
772 public void setPropertiesFile(File file) throws IOException {
773 ProductDerivations.load(file, null, getClass().getClassLoader()).
774 setInto(this);
775 _auto = file.toString();
776 }
777
778 /**
779 * Return the resource that was set via auto-configuration methods
780 * {@link #setProperties} or {@link #setPropertiesFile}, or null if none.
781 */
782 public String getPropertiesResource() {
783 return _auto;
784 }
785
786 /////////////
787 // Utilities
788 /////////////
789
790 /**
791 * Performs an equality check based on equality of values.
792 * {@link Value#equals(Object) Equality} of Values varies if the Value is
793 * {@link Value#isDynamic() dynamic}.
794 */
795 public boolean equals(Object other) {
796 if (other == this)
797 return true;
798 if (other == null)
799 return false;
800 if (!getClass().equals(other.getClass()))
801 return false;
802
803 // compare properties
804 ConfigurationImpl conf = (ConfigurationImpl) other;
805 if (_vals.size() != conf.getValues().length)
806 return false;
807 Iterator values = _vals.iterator();
808 while (values.hasNext()) {
809 Value v = (Value)values.next();
810 Value thatV = conf.getValue(v.getProperty());
811 if (!v.equals(thatV)) {
812 return false;
813 }
814 }
815 return true;
816 }
817
818 /**
819 * Computes hash code based on the hashCodes of the values.
820 * {@link Value#hashCode() HashCode} of a Value varies if the Value is
821 * {@link Value#isDynamic() dynamic}.
822 */
823 public int hashCode() {
824 Iterator values = _vals.iterator();
825 int hash = 0;
826 while (values.hasNext()) {
827 Value v = (Value)values.next();
828 hash += v.hashCode();
829 }
830 return hash;
831 }
832
833 /**
834 * Convert <code>propName</code> to a lowercase-with-hyphens-style string.
835 * This algorithm is only designed for mixes of uppercase and lowercase
836 * letters and lone digits. A more sophisticated conversion should probably
837 * be handled by a proper parser generator or regular expressions.
838 */
839 public static String toXMLName(String propName) {
840 if (propName == null)
841 return null;
842 StringBuffer buf = new StringBuffer();
843 char c;
844 for (int i = 0; i < propName.length(); i++) {
845 c = propName.charAt(i);
846
847 // convert sequences of all-caps to downcase with dashes around
848 // them. put a trailing cap that is followed by downcase into the
849 // downcase word.
850 if (i != 0 && Character.isUpperCase(c)
851 && (Character.isLowerCase(propName.charAt(i-1))
852 || (i > 1 && i < propName.length() - 1
853 && Character.isUpperCase(propName.charAt(i-1))
854 && Character.isLowerCase(propName.charAt(i+1)))))
855 buf.append('-');
856
857 // surround sequences of digits with dashes.
858 if (i != 0
859 && ((!Character.isLetter(c) && Character.isLetter(propName
860 .charAt(i - 1)))
861 || (Character.isLetter(c) && !Character.isLetter(propName
862 .charAt(i - 1)))))
863 buf.append('-');
864
865 buf.append(Character.toLowerCase(c));
866 }
867 return buf.toString();
868 }
869
870 /**
871 * Implementation of the {@link Externalizable} interface to read from
872 * the properties written by {@link #writeExternal}.
873 */
874 public void readExternal(ObjectInput in)
875 throws IOException, ClassNotFoundException {
876 fromProperties((Map) in.readObject());
877 _props = (Map) in.readObject();
878 _globals = in.readBoolean();
879 }
880
881 /**
882 * Implementation of the {@link Externalizable} interface to write
883 * the properties returned by {@link #toProperties}.
884 */
885 public void writeExternal(ObjectOutput out) throws IOException {
886 out.writeObject(toProperties(true));
887 out.writeObject(_props);
888 out.writeBoolean(_globals);
889 }
890
891 /**
892 * Uses {@link #toProperties} and {@link #fromProperties} to clone
893 * configuration.
894 */
895 public Object clone() {
896 try {
897 Constructor cons = getClass().getConstructor
898 (new Class[]{ boolean.class });
899 ConfigurationImpl clone = (ConfigurationImpl) cons.newInstance
900 (new Object[]{ Boolean.FALSE });
901 clone.fromProperties(toProperties(true));
902 clone._props = (_props == null) ? null : new HashMap(_props);
903 clone._globals = _globals;
904 return clone;
905 } catch (RuntimeException re) {
906 throw re;
907 } catch (Exception e) {
908 throw new ParseException(e);
909 }
910 }
911
912 public boolean removeValue(Value val) {
913 if (!_vals.remove(val))
914 return false;
915 val.setListener(null);
916 return true;
917 }
918
919 public Value addValue(Value val) {
920 _vals.add(val);
921 val.setListener(this);
922 return val;
923 }
924
925 /**
926 * Add the given value to the set of configuration properties.
927 */
928 public StringValue addString(String property) {
929 StringValue val = new StringValue(property);
930 addValue(val);
931 return val;
932 }
933
934 /**
935 * Add the given value to the set of configuration properties.
936 */
937 public FileValue addFile(String property) {
938 FileValue val = new FileValue(property);
939 addValue(val);
940 return val;
941 }
942
943 /**
944 * Add the given value to the set of configuration properties.
945 */
946 public IntValue addInt(String property) {
947 IntValue val = new IntValue(property);
948 addValue(val);
949 return val;
950 }
951
952 /**
953 * Add the given value to the set of configuration properties.
954 */
955 public DoubleValue addDouble(String property) {
956 DoubleValue val = new DoubleValue(property);
957 addValue(val);
958 return val;
959 }
960
961 /**
962 * Add the given value to the set of configuration properties.
963 */
964 public BooleanValue addBoolean(String property) {
965 BooleanValue val = new BooleanValue(property);
966 addValue(val);
967 val.setDefault("false");
968 return val;
969 }
970
971 /**
972 * Add the given value to the set of configuration properties.
973 */
974 public StringListValue addStringList(String property) {
975 StringListValue val = new StringListValue(property);
976 addValue(val);
977 return val;
978 }
979
980 /**
981 * Add the given value to the set of configuration properties.
982 */
983 public ObjectValue addObject(String property) {
984 ObjectValue val = new ObjectValue(property);
985 addValue(val);
986 return val;
987 }
988
989 /**
990 * Add the given value to the set of configuration properties.
991 */
992 public PluginValue addPlugin(String property, boolean singleton) {
993 PluginValue val = new PluginValue(property, singleton);
994 addValue(val);
995 return val;
996 }
997
998 /**
999 * Add the given value to the set of configuration properties.
1000 */
1001 public PluginListValue addPluginList(String property) {
1002 PluginListValue val = new PluginListValue(property);
1003 addValue(val);
1004 return val;
1005 }
1006 }