Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.fxml; 027 028import com.sun.javafx.fxml.BeanAdapter; 029import com.sun.javafx.fxml.builder.JavaFXFontBuilder; 030import com.sun.javafx.fxml.builder.JavaFXImageBuilder; 031import com.sun.javafx.fxml.builder.JavaFXSceneBuilder; 032import com.sun.javafx.fxml.builder.TriangleMeshBuilder; 033import com.sun.javafx.fxml.builder.URLBuilder; 034import java.lang.reflect.Array; 035import java.lang.reflect.InvocationTargetException; 036import java.lang.reflect.Method; 037import java.lang.reflect.Modifier; 038import java.net.URL; 039import java.util.AbstractMap; 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.Collection; 043import java.util.HashMap; 044import java.util.HashSet; 045import java.util.Iterator; 046import java.util.List; 047import java.util.Map; 048import java.util.Set; 049import java.util.logging.Level; 050import java.util.logging.Logger; 051import javafx.application.ConditionalFeature; 052import javafx.application.Platform; 053import javafx.collections.FXCollections; 054import javafx.collections.ObservableList; 055import javafx.collections.ObservableMap; 056import javafx.scene.Node; 057import javafx.scene.Scene; 058import javafx.scene.image.Image; 059import javafx.scene.shape.TriangleMesh; 060import javafx.scene.text.Font; 061import javafx.util.Builder; 062import javafx.util.BuilderFactory; 063import sun.reflect.misc.ConstructorUtil; 064import sun.reflect.misc.MethodUtil; 065 066/** 067 * JavaFX builder factory. 068 */ 069public final class JavaFXBuilderFactory implements BuilderFactory { 070 private final JavaFXBuilder NO_BUILDER = new JavaFXBuilder(); 071 072 private final Map<Class<?>, JavaFXBuilder> builders = new HashMap<Class<?>, JavaFXBuilder>(); 073 074 private final ClassLoader classLoader; 075 private final boolean alwaysUseBuilders; 076 077 private final boolean webSupported; 078 079 /** 080 * Default constructor. 081 */ 082 public JavaFXBuilderFactory() { 083 this(FXMLLoader.getDefaultClassLoader(), false); 084 } 085 086 /** 087 * @treatAsPrivate 088 * This constructor is for internal use only. 089 * 090 * @deprecated 091 */ 092 public JavaFXBuilderFactory(boolean alwaysUseBuilders) { 093 // SB-dependency: RT-21230 has been filed to track this 094 this(FXMLLoader.getDefaultClassLoader(), alwaysUseBuilders); 095 } 096 097 /** 098 * Constructor that takes a class loader. 099 * 100 * @param classLoader 101 */ 102 public JavaFXBuilderFactory(ClassLoader classLoader) { 103 this(classLoader, false); 104 } 105 106 /** 107 * @treatAsPrivate 108 * This constructor is for internal use only. 109 * 110 * @deprecated 111 */ 112 public JavaFXBuilderFactory(ClassLoader classLoader, boolean alwaysUseBuilders) { 113 // SB-dependency: RT-21230 has been filed to track this 114 if (classLoader == null) { 115 throw new NullPointerException(); 116 } 117 118 this.classLoader = classLoader; 119 this.alwaysUseBuilders = alwaysUseBuilders; 120 this.webSupported = Platform.isSupported(ConditionalFeature.WEB); 121 } 122 123 @Override 124 public Builder<?> getBuilder(Class<?> type) { 125 Builder<?> builder; 126 127 if (type == Scene.class) { 128 builder = new JavaFXSceneBuilder(); 129 } else if (type == Font.class) { 130 builder = new JavaFXFontBuilder(); 131 } else if (type == Image.class) { 132 builder = new JavaFXImageBuilder(); 133 } else if (type == URL.class) { 134 builder = new URLBuilder(classLoader); 135 } else if (type == TriangleMesh.class) { 136 builder = new TriangleMeshBuilder(); 137 } else { 138 Builder<Object> objectBuilder = null; 139 JavaFXBuilder typeBuilder = builders.get(type); 140 141 if (typeBuilder != NO_BUILDER) { 142 if (typeBuilder == null) { 143 // We want to retun a builder here 144 // only for those classes that reqire it. 145 // For now we assume that an object that has a default 146 // constructor does not require a builder. This is the case 147 // for most platform classes, except those handled above. 148 // We may need to add other exceptions to the rule if the need 149 // arises... 150 // 151 boolean hasDefaultConstructor; 152 try { 153 ConstructorUtil.getConstructor(type, new Class[] {}); 154 // found! 155 // forces the factory to return a builder if there is one. 156 // TODO: delete the line below when we are sure that both 157 // builders and default constructors are working! 158 if (alwaysUseBuilders) throw new Exception(); 159 160 hasDefaultConstructor = true; 161 } catch (Exception x) { 162 hasDefaultConstructor = false; 163 } 164 165 // Force the loader to use a builder for WebView even though 166 // it defines a default constructor 167 if (!hasDefaultConstructor || (webSupported && type.getName().equals("javafx.scene.web.WebView"))) { 168 try { 169 typeBuilder = createTypeBuilder(type); 170 } catch (ClassNotFoundException ex) { 171 // no builder... will fail later when the FXMLLoader 172 // will try to instantiate the bean... 173 } 174 } 175 176 builders.put(type, typeBuilder == null ? NO_BUILDER : typeBuilder); 177 178 } 179 if (typeBuilder != null) { 180 objectBuilder = typeBuilder.createBuilder(); 181 } 182 } 183 184 builder = objectBuilder; 185 } 186 187 return builder; 188 } 189 190 JavaFXBuilder createTypeBuilder(Class<?> type) throws ClassNotFoundException { 191 JavaFXBuilder typeBuilder = null; 192 Class<?> builderClass = classLoader.loadClass(type.getName() + "Builder"); 193 try { 194 typeBuilder = new JavaFXBuilder(builderClass); 195 } catch (Exception ex) { 196 //TODO should be reported 197 Logger.getLogger(JavaFXBuilderFactory.class.getName()). 198 log(Level.WARNING, "Failed to instantiate JavaFXBuilder for " + builderClass, ex); 199 } 200 if (!alwaysUseBuilders) { 201 Logger.getLogger(JavaFXBuilderFactory.class.getName()). 202 log(Level.FINER, "class {0} requires a builder.", type); 203 } 204 return typeBuilder; 205 } 206} 207 208/** 209 * JavaFX builder. 210 */ 211final class JavaFXBuilder { 212 private static final Object[] NO_ARGS = {}; 213 private static final Class<?>[] NO_SIG = {}; 214 215 private final Class<?> builderClass; 216 private final Method createMethod; 217 private final Method buildMethod; 218 private final Map<String,Method> methods = new HashMap<String, Method>(); 219 private final Map<String,Method> getters = new HashMap<String,Method>(); 220 private final Map<String,Method> setters = new HashMap<String,Method>(); 221 222 final class ObjectBuilder extends AbstractMap<String, Object> implements Builder<Object> { 223 private final Map<String,Object> containers = new HashMap<String,Object>(); 224 private Object builder = null; 225 private Map<Object,Object> properties; 226 227 private ObjectBuilder() { 228 try { 229 builder = MethodUtil.invoke(createMethod, null, NO_ARGS); 230 } catch (Exception e) { 231 //TODO 232 throw new RuntimeException("Creation of the builder " + builderClass.getName() + " failed.", e); 233 } 234 } 235 236 @Override 237 public Object build() { 238 for (Iterator<Entry<String,Object>> iter = containers.entrySet().iterator(); iter.hasNext(); ) { 239 Entry<String, Object> entry = iter.next(); 240 241 put(entry.getKey(), entry.getValue()); 242 } 243 244 Object res; 245 try { 246 res = MethodUtil.invoke(buildMethod, builder, NO_ARGS); 247 // TODO: 248 // temporary special case for Node properties until 249 // platform builders are fixed 250 if (properties != null && res instanceof Node) { 251 ((Map<Object, Object>)((Node)res).getProperties()).putAll(properties); 252 } 253 } catch (InvocationTargetException exception) { 254 throw new RuntimeException(exception); 255 } catch (IllegalAccessException exception) { 256 throw new RuntimeException(exception); 257 } finally { 258 builder = null; 259 } 260 261 return res; 262 } 263 264 @Override 265 public int size() { 266 throw new UnsupportedOperationException(); 267 } 268 269 @Override 270 public boolean isEmpty() { 271 throw new UnsupportedOperationException(); 272 } 273 274 @Override 275 public boolean containsKey(Object key) { 276 return (getTemporaryContainer(key.toString()) != null); 277 } 278 279 @Override 280 public boolean containsValue(Object value) { 281 throw new UnsupportedOperationException(); 282 } 283 284 @Override 285 public Object get(Object key) { 286 return getTemporaryContainer(key.toString()); 287 } 288 289 @Override 290 @SuppressWarnings("unchecked") 291 public Object put(String key, Object value) { 292 // TODO: 293 // temporary hack: builders don't have a method for properties... 294 if (Node.class.isAssignableFrom(getTargetClass()) && "properties".equals(key)) { 295 properties = (Map<Object,Object>) value; 296 return null; 297 } 298 try { 299 Method m = methods.get(key); 300 if (m == null) { 301 m = findMethod(key); 302 methods.put(key, m); 303 } 304 try { 305 final Class<?> type = m.getParameterTypes()[0]; 306 307 // If the type is an Array, and our value is a list, 308 // we simply convert the list into an array. Otherwise, 309 // we treat the value as a string and split it into a 310 // list using the array component delimiter. 311 if (type.isArray()) { 312 final List<?> list; 313 if (value instanceof List) { 314 list = (List<?>)value; 315 } else { 316 list = Arrays.asList(value.toString().split(FXMLLoader.ARRAY_COMPONENT_DELIMITER)); 317 } 318 319 final Class<?> componentType = type.getComponentType(); 320 Object array = Array.newInstance(componentType, list.size()); 321 for (int i=0; i<list.size(); i++) { 322 Array.set(array, i, BeanAdapter.coerce(list.get(i), componentType)); 323 } 324 value = array; 325 } 326 327 MethodUtil.invoke(m, builder, new Object[] { BeanAdapter.coerce(value, type) }); 328 } catch (Exception e) { 329 Logger.getLogger(JavaFXBuilder.class.getName()).log(Level.WARNING, 330 "Method " + m.getName() + " failed", e); 331 } 332 //TODO Is it OK to return null here? 333 return null; 334 } catch (Exception e) { 335 //TODO Should be reported 336 Logger.getLogger(JavaFXBuilder.class.getName()).log(Level.WARNING, 337 "Failed to set "+getTargetClass()+"."+key+" using "+builderClass, e); 338 return null; 339 } 340 } 341 342 // Should do this in BeanAdapter? 343 // This is used to support read-only collection property. 344 // This method must return a Collection of the appropriate type 345 // if 1. the property is read-only, and 2. the property is a collection. 346 // It must return null otherwise. 347 Object getReadOnlyProperty(String propName) { 348 if (setters.get(propName) != null) return null; 349 Method getter = getters.get(propName); 350 if (getter == null) { 351 Method setter = null; 352 Class<?> target = getTargetClass(); 353 String suffix = Character.toUpperCase(propName.charAt(0)) + propName.substring(1); 354 try { 355 getter = MethodUtil.getMethod(target, "get"+ suffix, NO_SIG); 356 setter = MethodUtil.getMethod(target, "set"+ suffix, new Class[] { getter.getReturnType() }); 357 } catch (Exception x) { 358 } 359 if (getter != null) { 360 getters.put(propName, getter); 361 setters.put(propName, setter); 362 } 363 if (setter != null) return null; 364 } 365 366 Class<?> type; 367 if (getter == null) { 368 // if we have found no getter it might be a constructor property 369 // try to get the type from the builder method. 370 final Method m = findMethod(propName); 371 if (m == null) { 372 return null; 373 } 374 type = m.getParameterTypes()[0]; 375 if (type.isArray()) type = List.class; 376 } else { 377 type = getter.getReturnType(); 378 } 379 380 if (ObservableMap.class.isAssignableFrom(type)) { 381 return FXCollections.observableMap(new HashMap<Object, Object>()); 382 } else if (Map.class.isAssignableFrom(type)) { 383 return new HashMap<Object, Object>(); 384 } else if (ObservableList.class.isAssignableFrom(type)) { 385 return FXCollections.observableArrayList(); 386 } else if (List.class.isAssignableFrom(type)) { 387 return new ArrayList<Object>(); 388 } else if (Set.class.isAssignableFrom(type)) { 389 return new HashSet<Object>(); 390 } 391 return null; 392 } 393 394 /** 395 * This is used to support read-only collection property. 396 * This method must return a Collection of the appropriate type 397 * if 1. the property is read-only, and 2. the property is a collection. 398 * It must return null otherwise. 399 **/ 400 public Object getTemporaryContainer(String propName) { 401 Object o = containers.get(propName); 402 if (o == null) { 403 o = getReadOnlyProperty(propName); 404 if (o != null) { 405 containers.put(propName, o); 406 } 407 } 408 409 return o; 410 } 411 412 @Override 413 public Object remove(Object key) { 414 throw new UnsupportedOperationException(); 415 } 416 417 @Override 418 public void putAll(Map<? extends String, ? extends Object> m) { 419 throw new UnsupportedOperationException(); 420 } 421 422 @Override 423 public void clear() { 424 throw new UnsupportedOperationException(); 425 } 426 427 @Override 428 public Set<String> keySet() { 429 throw new UnsupportedOperationException(); 430 } 431 432 @Override 433 public Collection<Object> values() { 434 throw new UnsupportedOperationException(); 435 } 436 437 @Override 438 public Set<Entry<String, Object>> entrySet() { 439 throw new UnsupportedOperationException(); 440 } 441 } 442 443 JavaFXBuilder() { 444 builderClass = null; 445 createMethod = null; 446 buildMethod = null; 447 } 448 449 JavaFXBuilder(Class<?> builderClass) throws NoSuchMethodException, InstantiationException, IllegalAccessException { 450 this.builderClass = builderClass; 451 createMethod = MethodUtil.getMethod(builderClass, "create", NO_SIG); 452 buildMethod = MethodUtil.getMethod(builderClass, "build", NO_SIG); 453 assert Modifier.isStatic(createMethod.getModifiers()); 454 assert !Modifier.isStatic(buildMethod.getModifiers()); 455 } 456 457 Builder<Object> createBuilder() { 458 return new ObjectBuilder(); 459 } 460 461 private Method findMethod(String name) { 462 if (name.length() > 1 463 && Character.isUpperCase(name.charAt(1))) { 464 name = Character.toUpperCase(name.charAt(0)) + name.substring(1); 465 } 466 467 for (Method m : MethodUtil.getMethods(builderClass)) { 468 if (m.getName().equals(name)) { 469 return m; 470 } 471 } 472 throw new IllegalArgumentException("Method " + name + " could not be found at class " + builderClass.getName()); 473 } 474 475 /** 476 * The type constructed by this builder. 477 * @return The type constructed by this builder. 478 */ 479 public Class<?> getTargetClass() { 480 return buildMethod.getReturnType(); 481 } 482}