001: /*
002: Copyright (c) 2003-2005, Dennis M. Sosnoski
003: All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without modification,
006: are permitted provided that the following conditions are met:
007:
008: * Redistributions of source code must retain the above copyright notice, this
009: list of conditions and the following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice,
011: this list of conditions and the following disclaimer in the documentation
012: and/or other materials provided with the distribution.
013: * Neither the name of JiBX nor the names of its contributors may be used
014: to endorse or promote products derived from this software without specific
015: prior written permission.
016:
017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: */
028:
029: package org.jibx.binding;
030:
031: import java.io.ByteArrayInputStream;
032: import java.io.ByteArrayOutputStream;
033: import java.io.File;
034: import java.io.FileInputStream;
035: import java.io.FileNotFoundException;
036: import java.io.IOException;
037: import java.io.InputStream;
038: import java.net.URL;
039: import java.util.ArrayList;
040: import java.util.jar.Attributes;
041: import java.util.jar.JarFile;
042: import java.util.jar.Manifest;
043:
044: import org.jibx.binding.classes.BoundClass;
045: import org.jibx.binding.classes.ClassFile;
046: import org.jibx.binding.def.BindingBuilder;
047: import org.jibx.binding.def.BindingDefinition;
048: import org.jibx.binding.def.MappingBase;
049: import org.jibx.binding.model.BindingElement;
050: import org.jibx.binding.model.IncludeElement;
051: import org.jibx.binding.model.MappingElement;
052: import org.jibx.binding.model.ValidationContext;
053: import org.jibx.runtime.JiBXException;
054: import org.jibx.runtime.impl.UnmarshallingContext;
055:
056: /**
057: * Binding compiler support class. Supplies common methods for use in compiling
058: * binding definitions.
059: *
060: * @author Dennis M. Sosnoski
061: * @version 1.0
062: */
063:
064: public class Utility {
065: // buffer size for copying stream input
066: private static final int COPY_BUFFER_SIZE = 1024;
067:
068: // private constructor to prevent any instance creation
069: private Utility() {
070: }
071:
072: /**
073: * Read contents of stream into byte array.
074: *
075: * @param is input stream to be read
076: * @return array of bytes containing all data from stream
077: * @throws IOException on stream access error
078: */
079: private static byte[] getStreamData(InputStream is)
080: throws IOException {
081: byte[] buff = new byte[COPY_BUFFER_SIZE];
082: ByteArrayOutputStream os = new ByteArrayOutputStream();
083: int count;
084: while ((count = is.read(buff)) >= 0) {
085: os.write(buff, 0, count);
086: }
087: return os.toByteArray();
088: }
089:
090: /**
091: * Recurse through jar file path component, adding all jars referenced from
092: * the original jar to the path collection. Silently ignores problems
093: * loading jar files.
094: *
095: * @param path jar path component
096: * @param paths set of paths processed (added to by call)
097: */
098: private static void recursePathJars(String path, ArrayList paths) {
099: try {
100:
101: // check class path information in jar file
102: JarFile jfile = new JarFile(path, false);
103: Manifest mfst = jfile.getManifest();
104: if (mfst != null) {
105:
106: // look for class path information from manifest
107: Attributes attrs = mfst.getMainAttributes();
108: String cpath = (String) attrs
109: .get(Attributes.Name.CLASS_PATH);
110: if (cpath != null) {
111:
112: // set base path for all relative references
113: int split = path.lastIndexOf(File.separatorChar);
114: String base = (split >= 0) ? path.substring(0,
115: split + 1) : "";
116:
117: // process all references in jar class path
118: while (cpath != null) {
119: split = cpath.indexOf(' ');
120: String item;
121: if (split >= 0) {
122: item = cpath.substring(0, split);
123: cpath = cpath.substring(split + 1).trim();
124: } else {
125: item = cpath;
126: cpath = null;
127: }
128: String ipath = base + item;
129: if (!paths.contains(ipath)) {
130: paths.add(ipath);
131: split = ipath.lastIndexOf('.');
132: if (split >= 0
133: && "jar".equalsIgnoreCase(ipath
134: .substring(split + 1))) {
135: recursePathJars(ipath, paths);
136: }
137: }
138: }
139: }
140: }
141: } catch (IOException ex) { /* silently ignore problems in loading */
142: }
143: }
144:
145: /**
146: * Method builds a string array of items in the class path.
147: *
148: * @return array of classpath components
149: */
150: public static String[] getClassPaths() {
151:
152: // get all class path components
153: String path = System.getProperty("java.class.path");
154: ArrayList paths = new ArrayList();
155: int start = 0;
156: int mark;
157: while (path != null) {
158: mark = path.indexOf(File.pathSeparatorChar, start);
159: String item;
160: if (mark >= 0) {
161: item = path.substring(start, mark);
162: } else {
163: item = path.substring(start);
164: path = null;
165: }
166: if (!paths.contains(item)) {
167: paths.add(item);
168: int split = item.lastIndexOf('.');
169: if (split >= 0
170: && "jar".equalsIgnoreCase(item
171: .substring(split + 1))) {
172: recursePathJars(item, paths);
173: }
174: }
175: start = mark + 1;
176: }
177: paths.add(".");
178: String[] clsspths = new String[paths.size()];
179: paths.toArray(clsspths);
180: return clsspths;
181: }
182:
183: /**
184: * Generate binding name. This takes a base name (such as a file name with
185: * extension stripped off) and converts it to legal form by substituting '_'
186: * characters for illegal characters in the base name.
187: *
188: * @param name base binding name
189: * @return converted binding name
190: */
191: public static String convertName(String name) {
192:
193: // convert name to use only legal characters
194: StringBuffer buff = new StringBuffer(name);
195: if (!Character.isJavaIdentifierStart(buff.charAt(0))) {
196: buff.insert(0, 'X');
197: }
198: for (int i = 1; i < buff.length(); i++) {
199: if (!Character.isJavaIdentifierPart(buff.charAt(i))) {
200: buff.setCharAt(i, '_');
201: }
202: }
203: return buff.toString();
204: }
205:
206: /**
207: * Extract base file name from a full path.
208: *
209: * @param path full file path
210: * @return file name component from path
211: */
212: public static String fileName(String path) {
213: int split = path.lastIndexOf(File.separatorChar);
214: return path.substring(split + 1);
215: }
216:
217: /**
218: * Validate binding definition. If issues are found in the binding the
219: * issues are printed directly to the console.
220: *
221: * @param name identifier for binding definition
222: * @param url URL for binding definition (<code>null</code> if not
223: * available)
224: * @param is input stream for reading binding definition
225: * @return root element of binding model if binding is valid,
226: * <code>null</code> if one or more errors in binding
227: */
228: public static BindingElement validateBinding(String name, URL url,
229: InputStream is) {
230: try {
231: ValidationContext vctx = BindingElement
232: .newValidationContext();
233: BindingElement root = BindingElement.validateBinding(name,
234: url, is, vctx);
235: if (vctx.getErrorCount() == 0 && vctx.getFatalCount() == 0) {
236: return root;
237: }
238: } catch (JiBXException ex) {
239: System.err.println("Unable to process binding " + name);
240: ex.printStackTrace();
241: }
242: return null;
243: }
244:
245: /**
246: * Load validated binding definition. This first reads the input stream into
247: * a memory buffer, then parses the data once for validation and a second
248: * time for the actual binding definition construction. If any errors are
249: * found in the binding definition validation the construction is skipped
250: * and an exception is thrown.
251: *
252: * @param fname binding definition full name
253: * @param sname short form of name to use as the default name of the binding
254: * @param istrm input stream for binding definition document
255: * @param url URL for binding definition (<code>null</code> if not
256: * available)
257: * @param test validate binding flag
258: * @return constructed binding definition
259: * @exception FileNotFoundException if path cannot be accessed
260: * @exception JiBXException if error in processing the binding definition
261: */
262: public static BindingDefinition loadBinding(String fname,
263: String sname, InputStream istrm, URL url, boolean test)
264: throws JiBXException, IOException {
265:
266: // read stream into memory buffer
267: byte[] data = getStreamData(istrm);
268:
269: // validate using binding object model
270: boolean valid = true;
271: ClassFile cf = null;
272: String tpack = null;
273: if (test) {
274: BindingElement root = validateBinding(fname, url,
275: new ByteArrayInputStream(data));
276: if (root == null) {
277: valid = false;
278: } else {
279:
280: // find package of first mapping to use for added classes
281: cf = findMappedClass(root);
282: tpack = root.getTargetPackage();
283: if (tpack == null && cf != null) {
284: tpack = cf.getPackage();
285: }
286: }
287: }
288: if (valid) {
289: try {
290: // construct the binding definition code generator
291: UnmarshallingContext uctx = new UnmarshallingContext();
292: uctx.setDocument(new ByteArrayInputStream(data), fname,
293: null);
294: if (cf != null) {
295:
296: // set target root and package for created classes
297: BoundClass.setModify(cf.getRoot(), tpack);
298: }
299: BindingDefinition bdef = BindingBuilder
300: .unmarshalBindingDefinition(uctx, sname, url);
301:
302: // set package and class if not validated
303: if (!test) {
304:
305: // a kludge, but needed to support skipping validation
306: ArrayList maps = bdef.getDefinitionContext()
307: .getMappings();
308: if (maps != null) {
309:
310: // set up package information from mapped class
311: for (int i = 0; i < maps.size(); i++) {
312: Object child = maps.get(i);
313: if (child instanceof MappingBase) {
314:
315: // end scan if a real mapping is found
316: MappingBase mapbase = (MappingBase) child;
317: cf = mapbase.getBoundClass()
318: .getMungedFile();
319: if (mapbase.getBoundClass()
320: .isDirectAccess()) {
321: break;
322: }
323:
324: }
325: }
326: }
327: }
328:
329: // set up binding root based on first mapping
330: if (cf == null) {
331: throw new JiBXException(
332: "One or more <mapping> elements "
333: + "must be defined in <binding>");
334: } else {
335:
336: // get package to be used for binding classes
337: if (tpack == null) {
338: tpack = bdef.getDefaultPackage();
339: if (tpack == null) {
340: tpack = cf.getPackage();
341: }
342: }
343: bdef.setFactoryLocation(tpack, cf.getRoot());
344: return bdef;
345: }
346:
347: } catch (JiBXException e) {
348: throw new JiBXException(
349: "\n*** Error during code generation for file '"
350: + fname
351: + "' - please enter a bug report for this error in Jira if "
352: + "the problem is not listed as fixed on the online status "
353: + "page ***\n", e);
354: }
355:
356: } else {
357: throw new JiBXException("Binding " + fname
358: + " is unusable because of validation errors");
359: }
360: }
361:
362: /**
363: * Recursively search through binding definitions for a modifiable mapped
364: * class. This is used to determine the default package for code generation.
365: *
366: * @param childs
367: * @return
368: */
369: private static ClassFile findMappedClass(BindingElement root) {
370: ArrayList childs = root.topChildren();
371: if (childs != null) {
372:
373: // recursively search for modifiable mapped class
374: for (int i = 0; i < childs.size(); i++) {
375: Object child = childs.get(i);
376: if (child instanceof MappingElement) {
377:
378: // end scan if a real mapping is found
379: MappingElement map = (MappingElement) child;
380: ClassFile cf = map.getHandledClass().getClassFile();
381: if (!cf.isInterface() && cf.isModifiable()) {
382: return cf;
383: }
384:
385: } else if (child instanceof IncludeElement) {
386:
387: // recurse on included binding
388: BindingElement bind = ((IncludeElement) child)
389: .getBinding();
390: if (bind != null) {
391: ClassFile cf = findMappedClass(bind);
392: if (cf != null) {
393: return cf;
394: }
395: }
396: }
397: }
398: }
399: return null;
400: }
401:
402: /**
403: * Load binding definition from file.
404: *
405: * @param path file path for binding definition
406: * @param valid validate binding flag
407: * @return constructed binding definition
408: * @exception IOException if error accessing file
409: * @exception JiBXException if error in processing the binding definition
410: */
411: public static BindingDefinition loadFileBinding(String path,
412: boolean valid) throws JiBXException, IOException {
413:
414: // extract basic name of binding file from path
415: String fname = fileName(path);
416: String sname = fname;
417: int split = sname.indexOf('.');
418: if (split > 0) {
419: sname = sname.substring(0, split);
420: }
421: sname = convertName(sname);
422:
423: // construct and return the binding definition
424: File file = new File(path);
425: return loadBinding(fname, sname, new FileInputStream(file),
426: file.toURL(), valid);
427: }
428: }
|