1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.oracle.javafx.scenebuilder.kit.metadata.util; 34 35 import java.io.File; 36 import java.net.MalformedURLException; 37 import java.net.URI; 38 import java.net.URISyntaxException; 39 import java.net.URL; 40 import java.nio.file.Path; 41 import java.nio.file.Paths; 42 import java.util.MissingResourceException; 43 import java.util.Objects; 44 import java.util.ResourceBundle; 45 import javafx.fxml.FXMLLoader; 46 47 /** 48 * 49 */ 50 public class PrefixedValue { 51 52 public enum Type { 53 DOCUMENT_RELATIVE_PATH, 54 CLASSLOADER_RELATIVE_PATH, 55 RESOURCE_KEY, 56 EXPRESSION, 57 PLAIN_STRING, 58 INVALID 59 } 60 61 private final String value; 62 private final Type type; 63 64 public PrefixedValue(String value) { 65 assert value != null; 66 this.value = value; 67 this.type = getPrefixedValueType(this.value); 68 } 69 70 public PrefixedValue(Type type, String suffix) { 71 assert type != Type.INVALID; 72 assert suffix != null; 73 74 this.type = type; 75 switch(this.type) { 76 case DOCUMENT_RELATIVE_PATH: { 77 final String encoding = encodePath(new File(suffix)); 78 this.value = FXMLLoader.RELATIVE_PATH_PREFIX + encoding; 79 break; 80 } 81 case CLASSLOADER_RELATIVE_PATH: { 82 final String encoding = encodePath(new File(suffix)); 83 this.value = FXMLLoader.RELATIVE_PATH_PREFIX + "/" + encoding; //NOI18N 84 break; 85 } 86 case RESOURCE_KEY: { 87 this.value = FXMLLoader.RESOURCE_KEY_PREFIX + suffix; //NOI18N 88 break; 89 } 90 case EXPRESSION: { 91 this.value = FXMLLoader.EXPRESSION_PREFIX + suffix; //NOI18N 92 break; 93 } 94 case PLAIN_STRING: { 95 if (suffix.startsWith(FXMLLoader.ESCAPE_PREFIX) 96 || suffix.startsWith(FXMLLoader.RELATIVE_PATH_PREFIX) 97 || suffix.startsWith(FXMLLoader.RESOURCE_KEY_PREFIX) 98 || suffix.startsWith(FXMLLoader.EXPRESSION_PREFIX)) { 99 this.value = FXMLLoader.ESCAPE_PREFIX + suffix; 100 } else { 101 this.value = suffix; 102 } 103 break; 104 } 105 default: 106 case INVALID: { 107 // Emergency code 108 throw new IllegalArgumentException("Unexpected type " + Type.INVALID); //NOI18N 109 } 110 } 111 } 112 113 public Type getType() { 114 return type; 115 } 116 117 public boolean isDocumentRelativePath() { 118 return type == Type.DOCUMENT_RELATIVE_PATH; 119 } 120 121 public boolean isClassLoaderRelativePath() { 122 return type == Type.CLASSLOADER_RELATIVE_PATH; 123 } 124 125 public boolean isResourceKey() { 126 return type == Type.RESOURCE_KEY; 127 } 128 129 public boolean isExpression() { 130 return type == Type.EXPRESSION; 131 } 132 133 public boolean isBindingExpression() { 134 return isExpression() && getSuffix().startsWith("{"); //NOI18N 135 } 136 137 public boolean isPlainString() { 138 return type == Type.PLAIN_STRING; 139 } 140 141 public boolean isInvalid() { 142 return type == Type.INVALID; 143 } 144 145 public String getSuffix() { 146 final String result; 147 148 switch(this.type) { 149 case DOCUMENT_RELATIVE_PATH: { 150 assert value.startsWith(FXMLLoader.RELATIVE_PATH_PREFIX); 151 final String encoding = value.substring(FXMLLoader.RELATIVE_PATH_PREFIX.length()); 152 result = decodePath(encoding).getPath(); 153 break; 154 } 155 case CLASSLOADER_RELATIVE_PATH: { 156 assert value.startsWith(FXMLLoader.RELATIVE_PATH_PREFIX+"/"); //NOI18N 157 final String encoding = value.substring(FXMLLoader.RELATIVE_PATH_PREFIX.length()+1); 158 result = decodePath(encoding).getPath(); 159 break; 160 } 161 case RESOURCE_KEY: { 162 assert value.startsWith(FXMLLoader.RESOURCE_KEY_PREFIX); 163 result = value.substring(FXMLLoader.RESOURCE_KEY_PREFIX.length()); 164 break; 165 } 166 case EXPRESSION: { 167 assert value.startsWith(FXMLLoader.EXPRESSION_PREFIX); 168 result = value.substring(FXMLLoader.EXPRESSION_PREFIX.length()); 169 break; 170 } 171 case PLAIN_STRING: { 172 if (value.startsWith(FXMLLoader.ESCAPE_PREFIX)) { 173 result = value.substring(FXMLLoader.ESCAPE_PREFIX.length()); 174 } else if (value.startsWith(FXMLLoader.RELATIVE_PATH_PREFIX+FXMLLoader.RELATIVE_PATH_PREFIX)) { 175 result = value.substring(FXMLLoader.RELATIVE_PATH_PREFIX.length()); 176 } else if (value.startsWith(FXMLLoader.RESOURCE_KEY_PREFIX+FXMLLoader.RESOURCE_KEY_PREFIX)) { 177 result = value.substring(FXMLLoader.RESOURCE_KEY_PREFIX.length()); 178 } else if (value.startsWith(FXMLLoader.EXPRESSION_PREFIX+FXMLLoader.EXPRESSION_PREFIX)) { 179 result = value.substring(FXMLLoader.EXPRESSION_PREFIX.length()); 180 } else { 181 result = value; 182 } 183 break; 184 } 185 default: 186 case INVALID: { 187 // Emergency code 188 result = null; 189 } 190 } 191 192 return result; 193 } 194 195 public URL resolveDocumentRelativePath(URL document) { 196 assert document != null; 197 assert type == Type.DOCUMENT_RELATIVE_PATH; 198 199 URL result; 200 try { 201 final String path = value.substring(FXMLLoader.RELATIVE_PATH_PREFIX.length()); 202 result = new URL(document, path); 203 } catch(MalformedURLException x) { 204 result = null; 205 } 206 207 return result; 208 } 209 210 public URL resolveClassLoaderRelativePath(ClassLoader classLoader) { 211 assert classLoader != null; 212 assert type == Type.CLASSLOADER_RELATIVE_PATH; 213 assert value.startsWith(FXMLLoader.RELATIVE_PATH_PREFIX+"/"); 214 215 final String path = value.substring(FXMLLoader.RELATIVE_PATH_PREFIX.length()+1); 216 return classLoader.getResource(path); 217 } 218 219 public String resolveResourceKey(ResourceBundle resources) { 220 assert resources != null; 221 assert type == Type.RESOURCE_KEY; 222 assert value.startsWith(FXMLLoader.RESOURCE_KEY_PREFIX); 223 224 String result; 225 try { 226 final String key = value.substring(FXMLLoader.RESOURCE_KEY_PREFIX.length()); 227 result = resources.getString(key); 228 } catch(MissingResourceException x) { 229 result = null; 230 } 231 232 return result; 233 } 234 235 public static Type getPrefixedValueType(String prefixedValue) { 236 final Type result; 237 238 String v = prefixedValue; 239 if (v.startsWith(FXMLLoader.ESCAPE_PREFIX)) { 240 v = v.substring(FXMLLoader.ESCAPE_PREFIX.length()); 241 if (v.isEmpty() 242 || !(v.startsWith(FXMLLoader.ESCAPE_PREFIX) 243 || v.startsWith(FXMLLoader.RELATIVE_PATH_PREFIX) 244 || v.startsWith(FXMLLoader.RESOURCE_KEY_PREFIX) 245 || v.startsWith(FXMLLoader.EXPRESSION_PREFIX) 246 || v.startsWith(FXMLLoader.BI_DIRECTIONAL_BINDING_PREFIX))) { 247 result = Type.INVALID; 248 } else { 249 result = Type.PLAIN_STRING; 250 } 251 } else if (v.startsWith(FXMLLoader.RELATIVE_PATH_PREFIX)) { 252 v = v.substring(FXMLLoader.RELATIVE_PATH_PREFIX.length()); 253 if (v.isEmpty()) { 254 result = Type.INVALID; 255 } else if (v.startsWith(FXMLLoader.RELATIVE_PATH_PREFIX)) { 256 // The prefix was escaped 257 result = Type.PLAIN_STRING; 258 } else if (v.charAt(0) == '/') { 259 result = Type.CLASSLOADER_RELATIVE_PATH; 260 } else { 261 result = Type.DOCUMENT_RELATIVE_PATH; 262 } 263 } else if (v.startsWith(FXMLLoader.RESOURCE_KEY_PREFIX)) { 264 v = v.substring(FXMLLoader.RESOURCE_KEY_PREFIX.length()); 265 if (v.isEmpty()) { 266 result = Type.INVALID; 267 } else if (v.startsWith(FXMLLoader.RESOURCE_KEY_PREFIX)) { 268 // The prefix was escaped 269 result = Type.PLAIN_STRING; 270 } else { 271 result = Type.RESOURCE_KEY; 272 } 273 } else if (v.startsWith(FXMLLoader.EXPRESSION_PREFIX)) { 274 v = v.substring(FXMLLoader.EXPRESSION_PREFIX.length()); 275 if (v.isEmpty()) { 276 result = Type.INVALID; 277 } else if (v.startsWith(FXMLLoader.EXPRESSION_PREFIX)) { 278 // The prefix was escaped 279 result = Type.PLAIN_STRING; 280 } else { 281 result = Type.EXPRESSION; 282 } 283 } else { 284 result = Type.PLAIN_STRING; 285 } 286 287 return result; 288 } 289 290 public static PrefixedValue makePrefixedValue(URL assetURL, URL documentURL) { 291 292 final File assetFile, documentFile; 293 try { 294 assetFile = new File(assetURL.toURI()); 295 documentFile = new File(documentURL.toURI()); 296 } catch(URISyntaxException x) { 297 throw new IllegalArgumentException(x); 298 } 299 final File parentFile = documentFile.getParentFile(); 300 301 final PrefixedValue result; 302 if ((parentFile == null) || parentFile.equals(assetFile)) { 303 throw new IllegalArgumentException(documentURL.toString()); 304 } else { 305 final Path relativePath = parentFile.toPath().relativize(assetFile.toPath()); 306 result = new PrefixedValue(Type.DOCUMENT_RELATIVE_PATH, relativePath.toString()); 307 } 308 309 return result; 310 } 311 312 313 /* 314 * Object 315 */ 316 317 @Override 318 public String toString() { 319 return value; 320 } 321 322 @Override 323 public int hashCode() { 324 int hash = 5; 325 hash = 97 * hash + Objects.hashCode(getSuffix()); 326 hash = 97 * hash + Objects.hashCode(this.type); 327 return hash; 328 } 329 330 @Override 331 public boolean equals(Object obj) { 332 if (obj == null) { 333 return false; 334 } 335 if (getClass() != obj.getClass()) { 336 return false; 337 } 338 final PrefixedValue other = (PrefixedValue) obj; 339 if (!Objects.equals(this.getSuffix(), other.getSuffix())) { 340 return false; 341 } 342 if (this.type != other.type) { 343 return false; 344 } 345 return true; 346 } 347 348 349 /* 350 * Private 351 */ 352 353 private static String encodePath(File file) { 354 final String result; 355 356 try { 357 if (file.isAbsolute()) { 358 result = file.toURI().toURL().getPath(); 359 } else { 360 final Path tmpPath = Paths.get(System.getProperty("java.io.tmpdir")); //NOI18N 361 final String tmpPathEncoding = tmpPath.toFile().toURI().toURL().getPath(); 362 final Path absolutePath = tmpPath.resolve(file.toPath()); 363 final String absoluteEncoding = absolutePath.toFile().toURI().toURL().getPath(); 364 assert absoluteEncoding.startsWith(tmpPathEncoding); 365 result = absoluteEncoding.substring(tmpPathEncoding.length()); 366 } 367 } catch(MalformedURLException x) { 368 throw new IllegalStateException(x); 369 } 370 371 return result; 372 } 373 374 private static File decodePath(String encoding) { 375 File result; 376 377 try { 378 if (encoding.startsWith("/")) { //NOI18N 379 result = new File(new URI("file:" + encoding)) ; //NOI18N 380 } else { 381 final Path tmpPath = Paths.get(System.getProperty("java.io.tmpdir")); //NOI18N 382 final URL tmpPathURL = tmpPath.toFile().toURI().toURL(); 383 final URL absoluteURL = new URL(tmpPathURL.toString() + "/" + encoding); //NOI18N 384 final File absoluteFile = new File(absoluteURL.toURI()); 385 final Path absolutePath = absoluteFile.toPath(); 386 assert absolutePath.startsWith(tmpPath); 387 final Path relativePath = tmpPath.relativize(absolutePath); 388 result = relativePath.toFile(); 389 } 390 391 assert encoding.equals(encodePath(result)); 392 393 } catch(MalformedURLException | URISyntaxException x) { 394 result = new File(encoding); 395 } 396 397 398 return result; 399 } 400 }