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 return buildComponent( root, null ); 087 } 088 catch( Throwable e ) 089 { 090 final String error = 091 "An error while attempting to load a component directive." 092 + "\nURI: " + uri; 093 IOException exception = new IOException( error ); 094 exception.initCause( e ); 095 throw exception; 096 } 097 } 098 099 /** 100 * Construct a component directive using the supplied DOM element. 101 * @param root the element representing the component directive definition 102 * @param resolver build-time uri resolver 103 * @return the component directive 104 * @exception DecodingException if an error occurs during directive creation 105 */ 106 public ComponentDirective buildComponent( Element root, Resolver resolver ) throws DecodingException 107 { 108 if( null == root ) 109 { 110 throw new NullPointerException( "root" ); 111 } 112 String tag = root.getTagName(); 113 if( "component".equals( tag ) ) 114 { 115 return createComponentDirective( root, resolver ); 116 } 117 else 118 { 119 final String error = 120 "Component directive element name [" 121 + tag 122 + "] is not recognized."; 123 throw new DecodingException( root, error ); 124 } 125 } 126 127 private ComponentDirective createComponentDirective( 128 Element element, Resolver resolver ) throws DecodingException 129 { 130 String classname = buildComponentClassname( element, resolver ); 131 String name = buildComponentName( element, resolver ); 132 ActivationPolicy activation = buildActivationPolicy( element, resolver ); 133 CollectionPolicy collection = buildCollectionPolicy( element, resolver ); 134 LifestylePolicy lifestyle = buildLifestylePolicy( element, resolver ); 135 CategoriesDirective categories = getNestedCategoriesDirective( element, resolver ); 136 ContextDirective context = getNestedContextDirective( element, resolver ); 137 PartReference[] parts = getNestedParts( element, resolver ); 138 URI base = getBaseURI( element, resolver ); 139 140 if( null == base ) 141 { 142 if( null == classname ) 143 { 144 final String error = 145 "Missing component type attribute."; 146 throw new DecodingException( element, error ); 147 } 148 } 149 else 150 { 151 if( null != classname ) 152 { 153 final String error = 154 "llegal attempt to override a base type in a supertype."; 155 throw new DecodingException( element, error ); 156 } 157 } 158 159 try 160 { 161 return new ComponentDirective( 162 name, activation, collection, lifestyle, classname, 163 categories, context, parts, base ); 164 } 165 catch( Exception e ) 166 { 167 final String error = 168 "Component directive creation error."; 169 throw new DecodingException( element, error, e ); 170 } 171 } 172 173 private URI getBaseURI( Element element, Resolver resolver ) throws DecodingException 174 { 175 String base = ElementHelper.getAttribute( element, "uri", null, resolver ); 176 if( null == base ) 177 { 178 return null; 179 } 180 else 181 { 182 try 183 { 184 return resolver.toURI( base ); 185 } 186 catch( Exception e ) 187 { 188 final String error = 189 "Error resolving 'uri' attribute value: " 190 + base; 191 throw new DecodingException( element, error, e ); 192 } 193 } 194 } 195 196 private String buildComponentClassname( Element element, Resolver resolver ) throws DecodingException 197 { 198 return ElementHelper.getAttribute( element, "type", null, resolver ); 199 } 200 201 private ActivationPolicy buildActivationPolicy( Element element, Resolver resolver ) throws DecodingException 202 { 203 String policy = ElementHelper.getAttribute( element, "activation", null, resolver ); 204 if( null == policy ) 205 { 206 return null; 207 } 208 else 209 { 210 return ActivationPolicy.parse( policy ); 211 } 212 } 213 214 private LifestylePolicy buildLifestylePolicy( Element element, Resolver resolver ) throws DecodingException 215 { 216 String policy = ElementHelper.getAttribute( element, "lifestyle", null, resolver ); 217 if( null != policy ) 218 { 219 return LifestylePolicy.parse( policy ); 220 } 221 else 222 { 223 return null; 224 } 225 } 226 227 private CollectionPolicy buildCollectionPolicy( Element element, Resolver resolver ) throws DecodingException 228 { 229 String policy = ElementHelper.getAttribute( element, "collection", null, resolver ); 230 if( null != policy ) 231 { 232 return CollectionPolicy.parse( policy ); 233 } 234 else 235 { 236 return null; 237 } 238 } 239 240 private String buildComponentName( Element element, Resolver resolver ) 241 { 242 return ElementHelper.getAttribute( element, "name", null, resolver ); 243 } 244 245 private CategoriesDirective getNestedCategoriesDirective( Element root, Resolver resolver ) 246 { 247 Element element = ElementHelper.getChild( root, "categories" ); 248 if( null == element ) 249 { 250 return null; 251 } 252 else 253 { 254 return createCategoriesDirective( element, resolver ); 255 } 256 } 257 258 private CategoriesDirective createCategoriesDirective( Element element, Resolver resolver ) 259 { 260 if( null == element ) 261 { 262 return null; 263 } 264 else 265 { 266 String name = ElementHelper.getAttribute( element, "name", null, resolver ); 267 Priority priority = createPriority( element, resolver ); 268 String target = ElementHelper.getAttribute( element, "target", null, resolver ); 269 CategoryDirective[] categories = createCategoryDirectiveArray( element, resolver ); 270 return new CategoriesDirective( name, priority, target, categories ); 271 } 272 } 273 274 private CategoryDirective createCategoryDirective( Element element, Resolver resolver ) 275 { 276 String name = ElementHelper.getAttribute( element, "name", null, resolver ); 277 Priority priority = createPriority( element, resolver); 278 String target = ElementHelper.getAttribute( element, "target", null, resolver ); 279 return new CategoryDirective( name, priority, target ); 280 } 281 282 private CategoryDirective[] createCategoryDirectiveArray( Element element, Resolver resolver ) 283 { 284 Element[] children = ElementHelper.getChildren( element ); 285 CategoryDirective[] categories = new CategoryDirective[ children.length ]; 286 for( int i=0; i<categories.length; i++ ) 287 { 288 Element elem = children[i]; 289 if( "category".equals( elem.getTagName() ) ) 290 { 291 categories[i] = createCategoryDirective( elem, resolver ); 292 } 293 else 294 { 295 categories[i] = createCategoriesDirective( elem, resolver ); 296 } 297 } 298 return categories; 299 } 300 301 private Priority createPriority( Element element, Resolver resolver ) 302 { 303 String priority = ElementHelper.getAttribute( element, "priority", null, resolver ); 304 if( null == priority ) 305 { 306 return null; 307 } 308 else 309 { 310 return Priority.parse( priority ); 311 } 312 } 313 314 private ContextDirective getNestedContextDirective( Element root, Resolver resolver ) throws DecodingException 315 { 316 Element context = ElementHelper.getChild( root, "context" ); 317 if( null == context ) 318 { 319 return null; 320 } 321 else 322 { 323 return createContextDirective( context, resolver ); 324 } 325 } 326 327 private ContextDirective createContextDirective( Element element, Resolver resolver ) throws DecodingException 328 { 329 String classname = ElementHelper.getAttribute( element, "class", null, resolver ); 330 Element[] children = ElementHelper.getChildren( element ); 331 PartReference[] entries = new PartReference[ children.length ]; 332 for( int i=0; i<children.length; i++ ) 333 { 334 Element elem = children[i]; 335 entries[i] = createContextEntryPartReference( elem, resolver ); 336 } 337 return new ContextDirective( classname, entries ); 338 } 339 340 private PartReference createContextEntryPartReference( Element element, Resolver resolver ) throws DecodingException 341 { 342 String key = ElementHelper.getAttribute( element, "key", null, resolver ); 343 String spec = ElementHelper.getAttribute( element, "lookup", null, resolver ); 344 if( null != spec ) 345 { 346 LookupDirective directive = new LookupDirective( spec ); 347 return new PartReference( key, directive ); 348 } 349 else 350 { 351 String name = element.getTagName(); 352 if( "entry".equals( name ) ) 353 { 354 ValueDirective directive = buildValueDirective( element, resolver ); 355 return new PartReference( key, directive ); 356 } 357 else if( "context".equals( name ) ) 358 { 359 ContextDirective directive = createContextDirective( element, resolver ); 360 return new PartReference( key, directive ); 361 } 362 else 363 { 364 final String error = 365 "Context entry element is not recognized."; 366 throw new DecodingException( element, error ); 367 } 368 } 369 } 370 371 /** 372 * Build a value directive using a supplied DOM element. 373 * @param element the DOM element 374 * @return the value directive 375 */ 376 protected ValueDirective buildValueDirective( Element element, Resolver resolver ) 377 { 378 String classname = ElementHelper.getAttribute( element, "class", null, resolver ); 379 String method = ElementHelper.getAttribute( element, "method", null, resolver ); 380 Element[] elements = ElementHelper.getChildren( element, "param" ); 381 if( elements.length > 0 ) 382 { 383 Value[] values = VALUE_DECODER.decodeValues( elements, resolver ); 384 return new ValueDirective( classname, method, values ); 385 } 386 else 387 { 388 String value = ElementHelper.getAttribute( element, "value", null, resolver ); 389 return new ValueDirective( classname, method, value ); 390 } 391 } 392 393 private PartReference[] getNestedParts( Element root, Resolver resolver ) throws DecodingException 394 { 395 Element parts = ElementHelper.getChild( root, "parts" ); 396 if( null == parts ) 397 { 398 return null; 399 } 400 else 401 { 402 return createParts( parts, resolver ); 403 } 404 } 405 406 private PartReference[] createParts( Element element, Resolver resolver ) throws DecodingException 407 { 408 Element[] children = ElementHelper.getChildren( element ); 409 PartReference[] parts = new PartReference[ children.length ]; 410 for( int i=0; i<children.length; i++ ) 411 { 412 Element elem = children[i]; 413 parts[i] = createPartReference( elem, resolver ); 414 } 415 return parts; 416 } 417 418 private PartReference createPartReference( Element element, Resolver resolver ) throws DecodingException 419 { 420 String tag = element.getTagName(); 421 String key = ElementHelper.getAttribute( element, "key", null, resolver ); 422 int priority = getPriority( element, resolver ); 423 if( "component".equals( tag ) ) 424 { 425 ComponentDirective directive = buildComponent( element, resolver ); 426 return new PartReference( key, directive, priority ); 427 } 428 else 429 { 430 final String error = 431 "Component part element name [" 432 + tag 433 + "] is not recognized."; 434 throw new DecodingException( element, error ); 435 } 436 } 437 438 private int getPriority( Element element, Resolver resolver ) throws DecodingException 439 { 440 String priority = ElementHelper.getAttribute( element, "priority", "0", resolver ); 441 try 442 { 443 return Integer.parseInt( priority ); 444 } 445 catch( Exception e ) 446 { 447 final String error = 448 "Unable to parse priority value."; 449 throw new DecodingException( element, error, e ); 450 } 451 } 452 453 /** 454 * Internal utility to get the name of the class without the package name. Used 455 * when constructing a default component name. 456 * @param classname the fully qualified classname 457 * @return the short class name without the package name 458 */ 459 private String toName( String classname ) 460 { 461 int i = classname.lastIndexOf( "." ); 462 if( i == -1 ) 463 { 464 return classname.toLowerCase(); 465 } 466 else 467 { 468 return classname.substring( i + 1, classname.length() ).toLowerCase(); 469 } 470 } 471 472 }