001/** 002 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=# 003 * This file is part of the LDP4j Project: 004 * http://www.ldp4j.org/ 005 * 006 * Center for Open Middleware 007 * http://www.centeropenmiddleware.com/ 008 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=# 009 * Copyright (C) 2014 Center for Open Middleware. 010 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=# 011 * Licensed under the Apache License, Version 2.0 (the "License"); 012 * you may not use this file except in compliance with the License. 013 * You may obtain a copy of the License at 014 * 015 * http://www.apache.org/licenses/LICENSE-2.0 016 * 017 * Unless required by applicable law or agreed to in writing, software 018 * distributed under the License is distributed on an "AS IS" BASIS, 019 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 020 * See the License for the specific language governing permissions and 021 * limitations under the License. 022 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=# 023 * Artifact : org.ldp4j.framework:ldp4j-application-api:0.1.0 024 * Bundle : ldp4j-application-api-0.1.0.jar 025 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=# 026 */ 027package org.ldp4j.application.vocabulary; 028 029import java.lang.reflect.Array; 030import java.util.HashMap; 031import java.util.Iterator; 032import java.util.Map; 033import java.util.SortedMap; 034import java.util.TreeMap; 035import java.util.UUID; 036 037 038/** 039 * A base vocabulary implementation for creating immutable vocabularies that 040 * defines a collection of {@code ImmutableTerm}s. <br/> 041 * Purpose specific vocabularies should be implemented as follows: 042 * 043 * <pre> 044 * private static class CustomVocabulary extends 045 * AbstractImmutableVocabulary<ImmutableTerm> { 046 * 047 * private static final String NAMESPACE = "http://www.example.org/vocab"; 048 * private static final String PREFERRED_PREFIX = "test"; 049 * 050 * private static final String TERM_THREE = "termThree"; 051 * private static final String TERM_TWO = "termTwo"; 052 * private static final String TERM_ONE = "termOne"; 053 * 054 * public CustomVocabulary() { 055 * super(ImmutableTerm.class, NAMESPACE, PREFERRED_PREFIX); 056 * } 057 * 058 * private static final CustomVocabulary VOCABULARY; 059 * private static final Term ONE; 060 * private static final Term TWO; 061 * private static final Term THREE; 062 * 063 * static { 064 * VOCABULARY = new CustomVocabulary(); 065 * ONE = new ImmutableTerm(VOCABULARY, TERM_ONE); 066 * TWO = new ImmutableTerm(VOCABULARY, TERM_TWO); 067 * THREE = new ImmutableTerm(VOCABULARY, TERM_THREE); 068 * VOCABULARY.initialize(); 069 * } 070 * } 071 * </pre> 072 * 073 * Term related operations will fail with a {@code IllegalStateException} if 074 * they are used before the vocabulary is initialized properly. 075 * 076 * @version 1.0 077 * @since 1.0.0 078 * @author Miguel Esteban Gutiérrez 079 * @see ImmutableTerm 080 */ 081public abstract class AbstractImmutableVocabulary<T extends ImmutableTerm> implements Vocabulary<T> { 082 083 private static final long serialVersionUID = -6913490730122202939L; 084 085 private enum Status { 086 INITIALIZING("is already initialized"), 087 INITIALIZED("has not been initialized properly"), 088 ; 089 private final String explanation; 090 091 Status(String explanation) { 092 this.explanation = explanation; 093 } 094 095 String getFailure(String namespace) { 096 return "Vocabulary '"+namespace+"' "+explanation; 097 } 098 099 } 100 101 private final UUID id=UUID.randomUUID(); 102 103 private final String namespace; 104 private final String prefix; 105 private final Class<T> termClass; 106 107 private final Map<String,Integer> nameOrdinal=new HashMap<String,Integer>(); // NOSONAR 108 private final SortedMap<Integer,T> terms=new TreeMap<Integer,T>(); // NOSONAR 109 110 private int ordinal=-1; 111 112 private Status status=Status.INITIALIZING; 113 114 public AbstractImmutableVocabulary(Class<T> clazz, String namespace, String prefix) { 115 this.termClass = clazz; 116 this.namespace = namespace; 117 this.prefix = prefix; 118 } 119 120 private void checkStatus(Status status) { 121 if(!this.status.equals(status)) { 122 throw new IllegalStateException(status.getFailure(namespace)); 123 } 124 } 125 126 private void checkInitializationPrecondition(boolean precondition, String message, Object... args) { 127 if(!precondition) { 128 String failureHeader= 129 String.format( 130 "Vocabulary '%s' (%s) initialization failure: ", 131 this.namespace, 132 getClass().getCanonicalName()); 133 throw new IllegalArgumentException(failureHeader.concat(String.format(message,args))); 134 } 135 } 136 137 /** 138 * Get the unique identifier of the vocabulary. 139 * @return The identifier of the vocabulary. 140 * @see java.util.UUID 141 */ 142 public final UUID getId() { 143 return id; 144 } 145 146 /** 147 * {@inheritDoc} 148 */ 149 @Override 150 public final String getNamespace() { 151 return namespace; 152 } 153 154 /** 155 * {@inheritDoc} 156 */ 157 @Override 158 public final String getPreferredPrefix() { 159 return prefix; 160 } 161 162 /** 163 * {@inheritDoc} 164 */ 165 @Override 166 public final T[] terms() { 167 checkStatus(Status.INITIALIZED); 168 @SuppressWarnings("unchecked") 169 T[] array=(T[])Array.newInstance(termClass, ordinal); 170 return terms.values().toArray(array); 171 } 172 173 /** 174 * {@inheritDoc} 175 */ 176 @Override 177 public final int size() { 178 checkStatus(Status.INITIALIZED); 179 return ordinal; 180 } 181 182 /** 183 * {@inheritDoc} 184 */ 185 @Override 186 public final T fromName(String name) { 187 checkStatus(Status.INITIALIZED); 188 if(name==null) { 189 throw new IllegalArgumentException("Object 'name' cannot be null"); 190 } 191 Integer termOrdinal = nameOrdinal.get(name); 192 if(termOrdinal!=null) { 193 return terms.get(termOrdinal); 194 } 195 return null; 196 } 197 198 /** 199 * {@inheritDoc} 200 */ 201 @Override 202 public T fromOrdinal(int ordinal) { 203 checkStatus(Status.INITIALIZED); 204 if(ordinal<0 || this.ordinal<=ordinal) { 205 throw new IndexOutOfBoundsException("No term available with ordinal '"+ordinal+"'"); 206 } 207 return terms.get(ordinal); 208 } 209 210 /** 211 * {@inheritDoc} 212 */ 213 @Override 214 public final <V> T fromValue(V value) { 215 checkStatus(Status.INITIALIZED); 216 try { 217 @SuppressWarnings("unchecked") 218 TypeAdapter<T,V> adapter = (TypeAdapter<T, V>) TypeAdapter.createAdapter(termClass,value.getClass()); 219 for(T candidate:terms.values()) { 220 if(value.equals(adapter.adapt(candidate))) { 221 return candidate; 222 } 223 } 224 return null; 225 } catch (CannotAdaptClassesException e) { 226 throw new UnsupportedOperationException("Class '"+termClass.getCanonicalName()+" cannot be transformed to '"+value.getClass().getCanonicalName()+"'",e); 227 } 228 } 229 230 /** 231 * {@inheritDoc} 232 */ 233 @Override 234 public final Iterator<T> iterator() { 235 checkStatus(Status.INITIALIZED); 236 final Iterator<? extends T> iterator = terms.values().iterator(); 237 return new Iterator<T>() { 238 @Override 239 public boolean hasNext() { 240 return iterator.hasNext(); 241 } 242 243 @Override 244 public T next() { 245 return iterator.next(); 246 } 247 248 @Override 249 public void remove() { 250 throw new UnsupportedOperationException("Removal not supported"); 251 } 252 }; 253 } 254 255 /** 256 * Allow the reservation of term names during the initialization of the 257 * vocabulary. 258 * 259 * @param name 260 * then name of the term 261 * @return a number that represents the position in which the term was 262 * reserved 263 * @throws IllegalArgumentException 264 * if the specified name is not valid or it has been already 265 * reserved. 266 * @throws IllegalStateException 267 * if the vocabulary has been already initialized. 268 */ 269 protected final int reserveTermName(String name) { 270 checkStatus(Status.INITIALIZING); 271 checkInitializationPrecondition(TermUtils.isValidTermName(name), "Object '%s' is not a valid term name",name); 272 checkInitializationPrecondition(!this.nameOrdinal.containsKey(name),"Term '%s' has been already reserved",name); 273 this.nameOrdinal.put(name, ++this.ordinal); 274 return this.ordinal; 275 } 276 277 /** 278 * Upon reservation, the method enables registering the properly built 279 * immutable term instance. 280 * 281 * @param term 282 * the term to be registered. 283 * @throws IllegalArgumentException 284 * if the specified term instance cannot be registered, either 285 * because the ordinal is invalid or because the another term 286 * with the same name has already been registered. 287 * @throws IllegalStateException 288 * if the vocabulary has been already initialized. 289 */ 290 protected final <S extends ImmutableTerm> void registerTerm(S term) { 291 checkStatus(Status.INITIALIZING); 292 checkInitializationPrecondition(this.nameOrdinal.containsKey(term.name()),"Term '%s' has not been reserved",term.name()); 293 checkInitializationPrecondition(term.ordinal()>=0 && term.ordinal()<=this.ordinal,"invalid ordinal '%d' for reserved name '%s'",term.ordinal(),term.name()); 294 this.terms.put(term.ordinal(),this.termClass.cast(term)); 295 } 296 297 /** 298 * Complete the initialization of the vocabulary. Beyond this point the 299 * vocabulary can be put to use. 300 * 301 * @throws IllegalStateException 302 * if the vocabulary has been already initialized or not all the 303 * reserved names have been registered. 304 */ 305 protected final void initialize() { 306 checkStatus(Status.INITIALIZING); 307 if(!(terms.size()==nameOrdinal.size())) { 308 throw new IllegalStateException(String.format( 309 "Vocabulary '%s' (%s) initialization failure: not all reserved names have been registered", 310 namespace, 311 getClass().getCanonicalName())); 312 } 313 this.status=Status.INITIALIZED; 314 } 315 316}