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