001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010:
011: package org.mmbase.datatypes;
012:
013: import java.net.*;
014: import java.util.*;
015: import javax.xml.parsers.DocumentBuilder;
016: import org.xml.sax.InputSource;
017: import org.w3c.dom.*;
018:
019: import org.mmbase.bridge.Field;
020: import org.mmbase.core.util.Fields;
021: import org.mmbase.datatypes.util.xml.*;
022: import org.mmbase.util.*;
023: import org.mmbase.util.xml.DocumentReader;
024: import org.mmbase.util.logging.*;
025:
026: /**
027: * <p>
028: * This class contains various methods for manipulating DataType objects.
029: * It contains a static set of named DataType objects, with which it is possible to craete a set
030: * of datatypes that are accessable throught the MMBase application.
031: * This set contains, at the very least, the basic datatypes (a DataType for every
032: * 'MMBase' type, i.e. integer, string, etc).
033: * There can be only one DataType in a set with a given name, so it is not possible to have multiple
034: * registered datatypes with the same name.
035: * </p>
036: * <p>
037: * A number of other methods in this class deal with conversion, creating datatypes, and 'finishing'
038: * datatypes (locking a datatype to protect it form being changed).
039: *</p>
040: * @author Pierre van Rooden
041: * @since MMBase-1.8
042: * @version $Id: DataTypes.java,v 1.27 2008/01/28 16:31:29 michiel Exp $
043: */
044:
045: public class DataTypes {
046:
047: private static final Logger log = Logging
048: .getLoggerInstance(DataTypes.class);
049:
050: // the datatype collector containing named DataTypes for use throughout the application
051: private static final DataTypeCollector dataTypeCollector = DataTypeCollector
052: .createSystemDataTypeCollector();
053:
054: public static void initialize() {
055: // read the XML
056: // Watching will ptobably not work properly,
057: // as datatypes depend one ach other, and are are referred
058: // throughout the system.
059: // For the moment turn watching off.
060: // Not sure if it is needed anyway - it won't actually happen that often
061: log.trace("" + Constants.class); // make sure its static init is called, otherwise it goes horribly wrong.
062:
063: log.debug("Reading datatypes " + dataTypeCollector);
064: readDataTypes(ResourceLoader.getConfigurationRoot(),
065: "datatypes.xml");
066:
067: /*
068: try {
069: ResourceWatcher watcher = new ResourceWatcher(ResourceLoader.getConfigurationRoot()) {
070: public void onChange(String resource) {
071: readDataTypes(getResourceLoader(), resource);
072: }
073: };
074: watcher.add("datatypes.xml");
075: watcher.start();
076: watcher.onChange("datatypes.xml");
077: } catch (Throwable t) {
078: log.error(t.getClass().getName() + ": " + Logging.stackTrace(t));
079: }
080: */
081:
082: }
083:
084: private static void readFailedDependencies(
085: List<DependencyException> failed) {
086: ListIterator<DependencyException> i = failed.listIterator();
087: while (i.hasNext()) {
088: DependencyException de = i.next();
089: if (de.retry()) {
090: log.debug("Resolved " + de.getId() + " after all");
091: i.remove();
092: }
093: }
094: }
095:
096: /**
097: * Initialize the type handlers defaultly supported by the system, plus those configured in WEB-INF/config.
098: */
099: private static void readDataTypes(ResourceLoader loader,
100: String resource) {
101: List<URL> resources = loader.getResourceList(resource);
102: if (log.isDebugEnabled())
103: log.debug("Using " + resources);
104: ListIterator<URL> i = resources.listIterator();
105: List<DependencyException> failed = new ArrayList<DependencyException>();
106: while (i.hasNext())
107: i.next();
108: while (i.hasPrevious()) {
109: try {
110: URL u = i.previous();
111: URLConnection con = u.openConnection();
112: if (con.getDoInput()) {
113: InputSource dataTypesSource = new InputSource(con
114: .getInputStream());
115: dataTypesSource.setSystemId(u.toExternalForm());
116: DocumentBuilder db = DocumentReader
117: .getDocumentBuilder(true, true,
118: new XMLErrorHandler(),
119: new XMLEntityResolver(true,
120: DataTypeReader.class));
121: Document doc = db.parse(dataTypesSource);
122: Element dataTypesElement = doc.getDocumentElement(); // fieldtypedefinitons or datatypes element
123: failed.addAll(DataTypeReader.readDataTypes(
124: dataTypesElement, dataTypeCollector));
125: }
126: } catch (Exception e) {
127: log.error(e.getMessage(), e);
128: }
129: }
130: int previousFailedSize = -1;
131: while (failed.size() > 0 && failed.size() > previousFailedSize) {
132: previousFailedSize = failed.size();
133: log.debug(failed);
134: readFailedDependencies(failed);
135: }
136: if (failed.size() > 0) {
137: log.error("Failed " + failed);
138: }
139: if (log.isDebugEnabled())
140: log.debug(dataTypeCollector.toString());
141: }
142:
143: /**
144: * Create an instance of a DataType based on the class passed.
145: * The DataType returned is, if possible, a specialized DataType (such as {@link IntegerDataType})
146: * based on the MMBase Type that most closely matches the passed class. Otherwise, it is a generic DataType
147: * specific for that class (with generally means that it only supports basic functionality such as autocast).
148: * @param name The name of the datatype to create. If <code>null</code> is passed, the class name is used.
149: * @param classType The class of the datatype to create. If <code>null</code> is passed, the
150: * dataType returned is based on Object.class.
151: */
152: public static <C> BasicDataType<C> createDataType(String name,
153: Class<C> classType) {
154: int type = Fields.classToType(classType);
155: if (name == null && classType != null) {
156: name = classType.getName();
157: }
158: if (type != Field.TYPE_UNKNOWN || classType == null) {
159: return createDataType(name, type, classType.isPrimitive());
160: } else {
161: return new BasicDataType<C>(name, classType);
162: }
163: }
164:
165: /**
166: * Create an instance of a DataType based on the MMBase type passed.
167: *
168: * @param primitive in case of integer, long, float, double, boolean this
169: * parameter determines whether a primitive type or the wrapper class
170: * should be used
171: */
172: private static BasicDataType createDataType(String name, int type,
173: boolean primitive) {
174: switch (type) {
175: case Field.TYPE_BINARY:
176: return new BinaryDataType(name);
177: case Field.TYPE_INTEGER:
178: return new IntegerDataType(name, primitive);
179: case Field.TYPE_LONG:
180: return new LongDataType(name, primitive);
181: case Field.TYPE_FLOAT:
182: return new FloatDataType(name, primitive);
183: case Field.TYPE_DOUBLE:
184: return new DoubleDataType(name, primitive);
185: case Field.TYPE_BOOLEAN:
186: return new BooleanDataType(name, primitive);
187: case Field.TYPE_STRING:
188: return new StringDataType(name);
189: case Field.TYPE_XML:
190: return new XmlDataType(name);
191: case Field.TYPE_NODE:
192: return new NodeDataType(name);
193: case Field.TYPE_DATETIME:
194: return new DateTimeDataType(name);
195: case Field.TYPE_LIST:
196: return new ListDataType(name);
197: default:
198: return new BasicDataType(name);
199: }
200: }
201:
202: /**
203: * Create an instance of a DataType based on the MMBase type passed. In case
204: * a type is used that has both a primitive and a wrapper class the wrapped
205: * version will be used.
206: */
207: private static BasicDataType createDataType(String name, int type) {
208: return createDataType(name, type, false);
209: }
210:
211: /**
212: * Add an instance of a DataType to the set of data types that are available thoughout the application.
213: * The datatype should have a proper name, and not occur already in the set.
214: * Note that the datatype is finished when added (if it wasn't already), and can thereafter not be changed.
215: * @param dataType the datatype to add
216: * @return the dataType added.
217: * @throws IllegalArgumentException if the datatype does not have a name or already occurs in the set
218: */
219: public static DataType addFinalDataType(BasicDataType dataType) {
220: String name = dataType.getName();
221: if (name == null) {
222: throw new IllegalArgumentException("Passed datatype "
223: + dataType + " does not have a name assigned.");
224: }
225: if (dataTypeCollector.contains(name)) {
226: throw new IllegalArgumentException(
227: "The datatype "
228: + dataType
229: + " was passed, but a type with the same name occurs as : "
230: + getDataType(name));
231: }
232: dataTypeCollector.finish(dataType);
233: dataTypeCollector.addDataType(dataType);
234: return dataType;
235: }
236:
237: /**
238: * Returns a DataType from the the available set of datatypes accessible throughout the application,
239: * or <code>null</code> if that type does not exist.
240: * @param name the name of the DataType to look for
241: * @return A DataType instance or <code>null</code> if none can be found
242: */
243: public static synchronized BasicDataType getDataType(String name) {
244: return dataTypeCollector.getDataType(name);
245: }
246:
247: /**
248: * Returns a DataType instance.
249: * The system first tries to obtain a data type from the available set of datatypes
250: * accessible throughout the application. If a DataType of the passed name exists, a clone of that DataType is returned.
251: * Otherwise, a clone of the base DataType passed is returned.
252: * if the DataType with the passed name does not exist, and the value passed for the baseDataType is <code>null</code>,
253: * the method returns <code>null</code>.
254: * @param name the name of the DataType to look for
255: * @param baseDataType the dataType to match against. Can be <code>null</code>.
256: * @return A DataType instance or <code>null</code> if none can be instantiated
257: */
258: public static synchronized BasicDataType getDataTypeInstance(
259: String name, BasicDataType baseDataType) {
260: return dataTypeCollector
261: .getDataTypeInstance(name, baseDataType);
262: }
263:
264: /**
265: * Returns a DataType instance.
266: * The system first tries to obtain a type from the available set of datatypes
267: * accessible throughout the application. If a DataType of the passed name exists,
268: * a clone of that DataType is returned. Otherwise, an instance of a DataType based
269: * on the base type is returned.
270: * @param name the name of the DataType to look for
271: * @param type the base type to use for a default datatype instance
272: * @return A DataType instance
273: */
274: public static synchronized BasicDataType getDataTypeInstance(
275: String name, int type) {
276: return getDataTypeInstance(name, getDataType(type));
277: }
278:
279: /**
280: * Returns a ListDataType instance.
281: * The system first tries to obtain a type from the available set of datatypes
282: * accessible throughout the application. If a DataType of the passed name exists,
283: * a clone of that DataType is returned. Otherwise, an instance of a ListDataType based
284: * on the list item type is returned.
285: * @param name the name of the DataType to look for
286: * @param listItemType the base type to use for a default listdatatype instance
287: * (this type determines the type of the list elements)
288: * @return A ListDataType instance
289: */
290: public static synchronized ListDataType getListDataTypeInstance(
291: String name, int listItemType) {
292: return (ListDataType) getDataTypeInstance(name,
293: getListDataType(listItemType));
294: }
295:
296: /**
297: * Returns the basic DataType that matches the passed type.
298: * The datatype is retrieved from the available set of datatypes accessible throughout the application.
299: * If this datatype does not (yet) exists, an instance is automatically created and added.
300: * The datatype returned by this method is only useful for matching or cloning - it cannot be changed.
301: * @param type the base type whose DataType to return
302: * @return the DataType instance
303: */
304: public static synchronized BasicDataType getDataType(int type) {
305: String name = Fields.getTypeDescription(type).toLowerCase();
306: BasicDataType dataType = getDataType(name);
307: if (dataType == null) {
308: if (type == Field.TYPE_LIST) {
309: dataType = getListDataType(Field.TYPE_UNKNOWN);
310: } else {
311: dataType = createDataType(name, type);
312: // dataTypeCollector.finish(dataType); // will be finished when reading the XML.
313: dataTypeCollector.addDataType(dataType);
314: }
315: }
316: return dataType;
317: }
318:
319: /**
320: * Returns the basic ListDataType whose item's DataType matches the passed type.
321: * The datatype is retrieved from the available set of datatypes accessible throughout the application.
322: * If this datatype does not (yet) exists, an instance is automatically created and added.
323: * The datatype returned by this method is only useful for matching or cloning - it cannot be changed.
324: * @param listItemType the base type whose ListDataType to return
325: * @return the ListDataType instance
326: */
327: public static ListDataType getListDataType(int listItemType) {
328: String name = Fields.getTypeDescription(Field.TYPE_LIST)
329: .toLowerCase()
330: + "["
331: + Fields.getTypeDescription(listItemType).toLowerCase()
332: + "]";
333: ListDataType dataType = (ListDataType) getDataType(name);
334: if (dataType == null) {
335: dataType = (ListDataType) createDataType(name,
336: Field.TYPE_LIST);
337: dataType.setItemDataType(getDataType(listItemType));
338: dataTypeCollector.finish(dataType);
339: dataTypeCollector.addDataType(dataType);
340: }
341: return dataType;
342: }
343:
344: public static DataTypeCollector getSystemCollector() {
345: return dataTypeCollector;
346: }
347:
348: /**
349: * Returns a new XML completely describing the given DataType.
350: * This means that the XML will <em>not</em> have a base attribute.
351: */
352: public static Document toXml(DataType<?> dataType) {
353: // create an inheritance stack
354: LinkedList<DataType<?>> stack = new LinkedList<DataType<?>>();
355: stack.addFirst(dataType);
356: while (dataType.getOrigin() != null) {
357: dataType = dataType.getOrigin();
358: stack.addFirst(dataType);
359: }
360:
361: // new XML
362: Document doc = DocumentReader.getDocumentBuilder()
363: .newDocument();
364:
365: // iterate the stack to completely resolve everything.
366: Iterator<DataType<?>> i = stack.iterator();
367: dataType = i.next();
368: Element e = (Element) doc.importNode(dataType.toXml(), true);
369: doc.appendChild(e);
370: dataType.toXml(e);
371: while (i.hasNext()) {
372: dataType = i.next();
373: dataType.toXml(e);
374: }
375: DocumentReader.setPrefix(doc, DataType.XMLNS, "dt");
376: return doc;
377: }
378:
379: public static void main(String arg[]) throws Exception {
380: DataTypes.initialize();
381: DataType dt = DataTypes.getDataType(arg[0]);
382: if (dt == null) {
383: throw new Exception("No such datatyep " + arg[0]);
384: }
385: System.out.println(org.mmbase.util.xml.XMLWriter.write(
386: DataTypes.toXml(dt), true, true));
387: }
388:
389: }
|