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 }