001 /*
002 * Copyright 2006 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.metro.builder;
020
021 import java.io.IOException;
022 import java.net.URI;
023
024 import net.dpml.component.ActivationPolicy;
025
026 import net.dpml.metro.data.ContextDirective;
027 import net.dpml.metro.data.CategoryDirective;
028 import net.dpml.metro.data.CategoriesDirective;
029 import net.dpml.metro.data.ComponentDirective;
030 import net.dpml.metro.data.ValueDirective;
031 import net.dpml.metro.data.LookupDirective;
032
033 import net.dpml.metro.info.LifestylePolicy;
034 import net.dpml.metro.info.CollectionPolicy;
035 import net.dpml.metro.info.PartReference;
036 import net.dpml.metro.info.Priority;
037
038 import net.dpml.lang.ValueDecoder;
039 import net.dpml.lang.Value;
040
041 import net.dpml.util.Resolver;
042 import net.dpml.util.DOM3DocumentBuilder;
043 import net.dpml.util.ElementHelper;
044 import net.dpml.util.DecodingException;
045 import net.dpml.util.SimpleResolver;
046
047 import org.w3c.dom.Document;
048 import org.w3c.dom.Element;
049
050 /**
051 * Construct a state graph.
052 */
053 public class ComponentDecoder
054 {
055 private static final String STATE_SCHEMA_URN = "link:xsd:dpml/lang/dpml-state#1.0";
056
057 private static final String SCHEMA_URN = "link:xsd:dpml/lang/dpml-component#1.0";
058
059 private static final DOM3DocumentBuilder DOCUMENT_BUILDER = new DOM3DocumentBuilder();
060
061 private static final ComponentTypeDecoder TYPE_DECODER = new ComponentTypeDecoder();
062
063 private static final ValueDecoder VALUE_DECODER = new ValueDecoder();
064
065 /**
066 * Construct a component directive using the supplied uri. The uri
067 * must refer to an XML document containing a root component element
068 * (typically used in component data testcases).
069 *
070 * @param uri the part uri
071 * @return the component directive
072 * @exception IOException if an error occurs during directive creation
073 */
074 public ComponentDirective loadComponentDirective( URI uri ) throws IOException
075 {
076 if( null == uri )
077 {
078 throw new NullPointerException( "uri" );
079 }
080 try
081 {
082 final Document document = DOCUMENT_BUILDER.parse( uri );
083 final Element root = document.getDocumentElement();
084 Resolver resolver = new SimpleResolver();
085 return buildComponent( root, resolver );
086 }
087 catch( Throwable e )
088 {
089 final String error =
090 "An error while attempting to load a component directive."
091 + "\nURI: " + uri;
092 IOException exception = new IOException( error );
093 exception.initCause( e );
094 throw exception;
095 }
096 }
097
098 /**
099 * Construct a component directive using the supplied DOM element.
100 * @param root the element representing the component directive definition
101 * @param resolver build-time uri resolver
102 * @return the component directive
103 * @exception DecodingException if an error occurs during directive creation
104 */
105 public ComponentDirective buildComponent( Element root, Resolver resolver ) throws DecodingException
106 {
107 if( null == root )
108 {
109 throw new NullPointerException( "root" );
110 }
111 String tag = root.getTagName();
112 if( "component".equals( tag ) )
113 {
114 return createComponentDirective( root, resolver );
115 }
116 else
117 {
118 final String error =
119 "Component directive element name ["
120 + tag
121 + "] is not recognized.";
122 throw new DecodingException( root, error );
123 }
124 }
125
126 private ComponentDirective createComponentDirective(
127 Element element, Resolver resolver ) throws DecodingException
128 {
129 String classname = buildComponentClassname( element );
130 String name = buildComponentName( element );
131 ActivationPolicy activation = buildActivationPolicy( element );
132 CollectionPolicy collection = buildCollectionPolicy( element );
133 LifestylePolicy lifestyle = buildLifestylePolicy( element );
134 CategoriesDirective categories = getNestedCategoriesDirective( element );
135 ContextDirective context = getNestedContextDirective( element );
136 PartReference[] parts = getNestedParts( element, resolver );
137 URI base = getBaseURI( element, resolver );
138
139 if( null == base )
140 {
141 if( null == classname )
142 {
143 final String error =
144 "Missing component type attribute.";
145 throw new DecodingException( element, error );
146 }
147 }
148 else
149 {
150 if( null != classname )
151 {
152 final String error =
153 "llegal attempt to override a base type in a supertype.";
154 throw new DecodingException( element, error );
155 }
156 }
157
158 try
159 {
160 return new ComponentDirective(
161 name, activation, collection, lifestyle, classname,
162 categories, context, parts, base );
163 }
164 catch( Exception e )
165 {
166 final String error =
167 "Component directive creation error.";
168 throw new DecodingException( element, error, e );
169 }
170 }
171
172 private URI getBaseURI( Element element, Resolver resolver ) throws DecodingException
173 {
174 String base = ElementHelper.getAttribute( element, "uri" );
175 if( null == base )
176 {
177 return null;
178 }
179 else
180 {
181 try
182 {
183 return resolver.toURI( base );
184 }
185 catch( Exception e )
186 {
187 final String error =
188 "Error resolving 'uri' attribute value: "
189 + base;
190 throw new DecodingException( element, error, e );
191 }
192 }
193 }
194
195 private String buildComponentClassname( Element element ) throws DecodingException
196 {
197 return ElementHelper.getAttribute( element, "type" );
198 }
199
200 private ActivationPolicy buildActivationPolicy( Element element ) throws DecodingException
201 {
202 String policy = ElementHelper.getAttribute( element, "activation" );
203 if( null == policy )
204 {
205 return null;
206 }
207 else
208 {
209 return ActivationPolicy.parse( policy );
210 }
211 }
212
213 private LifestylePolicy buildLifestylePolicy( Element element ) throws DecodingException
214 {
215 String policy = ElementHelper.getAttribute( element, "lifestyle", null );
216 if( null != policy )
217 {
218 return LifestylePolicy.parse( policy );
219 }
220 else
221 {
222 return null;
223 }
224 }
225
226 private CollectionPolicy buildCollectionPolicy( Element element ) throws DecodingException
227 {
228 String policy = ElementHelper.getAttribute( element, "collection" );
229 if( null != policy )
230 {
231 return CollectionPolicy.parse( policy );
232 }
233 else
234 {
235 return null;
236 }
237 }
238
239 private String buildComponentName( Element element )
240 {
241 return ElementHelper.getAttribute( element, "name" );
242 }
243
244 private CategoriesDirective getNestedCategoriesDirective( Element root )
245 {
246 Element element = ElementHelper.getChild( root, "categories" );
247 if( null == element )
248 {
249 return null;
250 }
251 else
252 {
253 return createCategoriesDirective( element );
254 }
255 }
256
257 private CategoriesDirective createCategoriesDirective( Element element )
258 {
259 if( null == element )
260 {
261 return null;
262 }
263 else
264 {
265 String name = ElementHelper.getAttribute( element, "name" );
266 Priority priority = createPriority( element );
267 String target = ElementHelper.getAttribute( element, "target" );
268 CategoryDirective[] categories = createCategoryDirectiveArray( element );
269 return new CategoriesDirective( name, priority, target, categories );
270 }
271 }
272
273 private CategoryDirective createCategoryDirective( Element element )
274 {
275 String name = ElementHelper.getAttribute( element, "name" );
276 Priority priority = createPriority( element );
277 String target = ElementHelper.getAttribute( element, "target" );
278 return new CategoryDirective( name, priority, target );
279 }
280
281 private CategoryDirective[] createCategoryDirectiveArray( Element element )
282 {
283 Element[] children = ElementHelper.getChildren( element );
284 CategoryDirective[] categories = new CategoryDirective[ children.length ];
285 for( int i=0; i<categories.length; i++ )
286 {
287 Element elem = children[i];
288 if( "category".equals( elem.getTagName() ) )
289 {
290 categories[i] = createCategoryDirective( elem );
291 }
292 else
293 {
294 categories[i] = createCategoriesDirective( elem );
295 }
296 }
297 return categories;
298 }
299
300 private Priority createPriority( Element element )
301 {
302 String priority = ElementHelper.getAttribute( element, "priority" );
303 if( null == priority )
304 {
305 return null;
306 }
307 else
308 {
309 return Priority.parse( priority );
310 }
311 }
312
313 private ContextDirective getNestedContextDirective( Element root ) throws DecodingException
314 {
315 Element context = ElementHelper.getChild( root, "context" );
316 if( null == context )
317 {
318 return null;
319 }
320 else
321 {
322 return createContextDirective( context );
323 }
324 }
325
326 private ContextDirective createContextDirective( Element element ) throws DecodingException
327 {
328 String classname = ElementHelper.getAttribute( element, "class" );
329 Element[] children = ElementHelper.getChildren( element );
330 PartReference[] entries = new PartReference[ children.length ];
331 for( int i=0; i<children.length; i++ )
332 {
333 Element elem = children[i];
334 entries[i] = createContextEntryPartReference( elem );
335 }
336 return new ContextDirective( classname, entries );
337 }
338
339 private PartReference createContextEntryPartReference( Element element ) throws DecodingException
340 {
341 String key = ElementHelper.getAttribute( element, "key" );
342 String spec = ElementHelper.getAttribute( element, "lookup" );
343 if( null != spec )
344 {
345 LookupDirective directive = new LookupDirective( spec );
346 return new PartReference( key, directive );
347 }
348 else
349 {
350 String name = element.getTagName();
351 if( "entry".equals( name ) )
352 {
353 ValueDirective directive = buildValueDirective( element );
354 return new PartReference( key, directive );
355 }
356 //else if( "component".equals( name ) )
357 //{
358 // ComponentDirective directive = buildComponent( element );
359 // return new PartReference( key, directive );
360 //}
361 else
362 {
363 final String error =
364 "Context entry element is not recognized.";
365 throw new DecodingException( element, error );
366 }
367 }
368 }
369
370 /**
371 * Build a value directive using a supplied DOM element.
372 * @param element the DOM element
373 * @return the value directive
374 */
375 protected ValueDirective buildValueDirective( Element element )
376 {
377 String classname = ElementHelper.getAttribute( element, "class" );
378 String method = ElementHelper.getAttribute( element, "method" );
379 Element[] elements = ElementHelper.getChildren( element, "param" );
380 if( elements.length > 0 )
381 {
382 Value[] values = VALUE_DECODER.decodeValues( elements );
383 return new ValueDirective( classname, method, values );
384 }
385 else
386 {
387 String value = ElementHelper.getAttribute( element, "value" );
388 return new ValueDirective( classname, method, value );
389 }
390 }
391
392 private PartReference[] getNestedParts( Element root, Resolver resolver ) throws DecodingException
393 {
394 Element parts = ElementHelper.getChild( root, "parts" );
395 if( null == parts )
396 {
397 return null;
398 }
399 else
400 {
401 return createParts( parts, resolver );
402 }
403 }
404
405 private PartReference[] createParts( Element element, Resolver resolver ) throws DecodingException
406 {
407 Element[] children = ElementHelper.getChildren( element );
408 PartReference[] parts = new PartReference[ children.length ];
409 for( int i=0; i<children.length; i++ )
410 {
411 Element elem = children[i];
412 parts[i] = createPartReference( elem, resolver );
413 }
414 return parts;
415 }
416
417 private PartReference createPartReference( Element element, Resolver resolver ) throws DecodingException
418 {
419 String tag = element.getTagName();
420 String key = ElementHelper.getAttribute( element, "key" );
421 int priority = getPriority( element );
422 if( "component".equals( tag ) )
423 {
424 ComponentDirective directive = buildComponent( element, resolver );
425 return new PartReference( key, directive, priority );
426 }
427 else
428 {
429 final String error =
430 "Component part element name ["
431 + tag
432 + "] is not recognized.";
433 throw new DecodingException( element, error );
434 }
435 }
436
437 private int getPriority( Element element ) throws DecodingException
438 {
439 String priority = ElementHelper.getAttribute( element, "priority", "0" );
440 try
441 {
442 return Integer.parseInt( priority );
443 }
444 catch( Exception e )
445 {
446 final String error =
447 "Unable to parse priority value.";
448 throw new DecodingException( element, error, e );
449 }
450 }
451
452 /**
453 * Internal utility to get the name of the class without the package name. Used
454 * when constructing a default component name.
455 * @param classname the fully qualified classname
456 * @return the short class name without the package name
457 */
458 private String toName( String classname )
459 {
460 int i = classname.lastIndexOf( "." );
461 if( i == -1 )
462 {
463 return classname.toLowerCase();
464 }
465 else
466 {
467 return classname.substring( i + 1, classname.length() ).toLowerCase();
468 }
469 }
470
471 }