DefaultModuleAddress.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2011,  Michael Meyling <mime@qedeq.org>.
004  *
005  * "Hilbert II" is free software; you can redistribute
006  * it and/or modify it under the terms of the GNU General Public
007  * License as published by the Free Software Foundation; either
008  * version 2 of the License, or (at your option) any later version.
009  *
010  * This program is distributed in the hope that it will be useful,
011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013  * GNU General Public License for more details.
014  */
015 
016 package org.qedeq.kernel.se.common;
017 
018 import java.io.File;
019 import java.io.IOException;
020 import java.net.MalformedURLException;
021 import java.net.URL;
022 
023 import org.qedeq.base.io.IoUtility;
024 import org.qedeq.base.trace.Trace;
025 import org.qedeq.kernel.se.base.module.LocationList;
026 import org.qedeq.kernel.se.base.module.Specification;
027 
028 
029 /**
030  * An object of this class represents an address for a QEDEQ module.
031  *
032  @author  Michael Meyling
033  */
034 public class DefaultModuleAddress implements ModuleAddress {
035 
036     /** Default memory module address with identifier "default". */
037     public static final DefaultModuleAddress MEMORY = new DefaultModuleAddress();
038 
039     /** This class. */
040     private static final Class CLASS = DefaultModuleAddress.class;
041 
042     /** URL form of this address. */
043     private final String url;
044 
045     /** Header (including protocol, host, port, user) but without file path. */
046     private final String header;
047 
048     /** Path (without protocol, host, port and file name). */
049     private final String path;
050 
051     /** File name of QEDEQ module including <code>.xml</code>. */
052     private final String fileName;
053 
054     /** Is module address relative? */
055     private final boolean relativeAddress;
056 
057     /** Is module address a file? */
058     private final boolean fileAddress;
059 
060     /** Module name. That is file name without <code>.xml</code> */
061     private final String name;
062 
063     /**
064      * Constructor.
065      *
066      @param   u       Address of module. Must not be <code>null</code>.
067      *                  Must be a URL with protocol "file" or "http" and address a file
068      *                  with extension ".xml".
069      @throws  MalformedURLException    Address is formally incorrect.
070      */
071     public DefaultModuleAddress(final String uthrows MalformedURLException {
072         this(u, (DefaultModuleAddressnull);
073     }
074 
075     /**
076      * Constructor.
077      *
078      @param   u       Address of module. Must not be <code>null</code>.
079      *                  Must be a URL with protocol "file" or "http" and address a file
080      *                  with extension ".xml".
081      @throws  MalformedURLException    Address is formally incorrect.
082      */
083     public DefaultModuleAddress(final URL uthrows MalformedURLException {
084         this(u.toExternalForm()(ModuleAddressnull);
085     }
086 
087     /**
088      * Constructor.
089      *
090      @param   file    File path of module. Must address a file
091      *                  with extension ".xml".
092      @throws  IOException Problem with file location.
093      */
094     public DefaultModuleAddress(final File filethrows IOException {
095         this(IoUtility.toUrl(file.getCanonicalFile()));
096     }
097 
098     /**
099      * Default constructor for memory modules.
100      */
101     public DefaultModuleAddress() {
102         this(true, "default");
103     }
104 
105     /**
106      * Constructor mainly used for memory modules.
107      * TODO 20110227 m31: this is no object oriented design: a parameter must be true???
108      *                    refactor code and create extra memory module address class!
109      *
110      @param   memory      Must be true. If not a runtime exception occurs.
111      @param   identifier  Identifies the module in memory.
112      */
113     public DefaultModuleAddress(final boolean memory, final String identifier) {
114         if (!memory) {
115             throw new IllegalArgumentException("memory must be true");
116         }
117         url = "memory://" + identifier;
118         name = identifier;
119         fileAddress = false;
120         fileName = identifier;
121         header = "memory:";
122         path = "";
123         relativeAddress = true;
124     }
125 
126     /**
127      * Constructor.
128      *
129      @param   address  Address of module. Must not be <code>null</code>.
130      *                  Must be a URL with protocol "file" or "http" (if <code>parent</code> is
131      *                  <code>null</code>) and address a file
132      *                  with extension ".xml".
133      @param   parent   Address of parent module. Can be <code>null</code>.
134      @throws  MalformedURLException     Address is formally incorrect.
135      */
136     public DefaultModuleAddress(final String address, final ModuleAddress parent)
137             throws MalformedURLException {
138         final String method = "ModuleAddress(String, ModuleAddress)";
139         if (address == null) {
140             throw new NullPointerException();
141         }
142         URL urmel;
143         try {
144             if (parent != null) {
145                 urmel = new URL(new URL(parent.getUrl()), address);
146             else {
147                 urmel = new URL(address);
148             }
149         catch (MalformedURLException e) {
150             Trace.trace(CLASS, this, method, "address=" + address);
151             Trace.trace(CLASS, this, method, "parent=" + parent);
152             Trace.trace(CLASS, this, method, e);
153             try {
154                 final String newAddress = "file:" + address;
155                 if (parent != null) {
156                     urmel = new URL(new URL(parent.getUrl()), newAddress);
157                 else {
158                     urmel = new URL(newAddress);
159                 }
160             catch (MalformedURLException ex) {
161                 throw e;    // throw original exception
162             }
163         }
164         Trace.trace(CLASS, this, method, "protocol=" + urmel.getProtocol());
165         fileAddress = urmel.getProtocol().equalsIgnoreCase("file");
166         if (!fileAddress) {
167             url = urmel.toString();
168         else {
169             String urm = urmel.toString();
170             if (urm.startsWith("file:"&& !urm.startsWith("file://")) {
171                 urm = "file://" + urm.substring("file:".length());
172             }
173             url = urm;
174         }
175 /*
176         Trace.trace(this, METHOD, "url.getFile=" + this.url.getFile());
177         Trace.trace(this, METHOD, "url.getPath=" + this.url.getPath());
178         try {
179             Trace.trace(this, METHOD, "URI File=" +
180                 new File(new URI(this.address)).getAbsoluteFile());
181         } catch (URISyntaxException e1) {
182             e1.printStackTrace();
183         }
184 */
185         final String p = urmel.getFile();
186         final int position = p.lastIndexOf("/");
187         if (position >= && position + < p.length()) {
188             this.path = p.substring(0, position"/";
189             this.fileName = p.substring(position + 1);
190         else {
191             this.path = "";
192             this.fileName = p;
193         }
194         Trace.trace(CLASS, this, method, "path=" this.path);
195         Trace.trace(CLASS, this, method, "fileName=" this.fileName);
196         this.relativeAddress = !this.path.startsWith("/");
197         if (!this.fileName.endsWith(".xml")) {
198             throw new MalformedURLException("file name doesn't end with \".xml\": "
199                 this.fileName);
200         }
201         final int positionBefore = this.fileName.lastIndexOf(".");
202         final String mname = this.fileName.substring(0, positionBefore);
203         this.name = mname;
204         final int positionPath = url.lastIndexOf(this.path + this.fileName);
205         if (positionPath < 0) {
206             throw new IllegalArgumentException(
207                 "couldn't determine begin of file path: "
208                 + url);
209         }
210         this.header = url.substring(0, positionPath);
211     }
212 
213     /**
214      * Get module address as {@link ModuleContext}. Creates a new object.
215      *
216      @return  Module address as {@link ModuleContext}.
217      */
218     public ModuleContext createModuleContext() {
219         return new ModuleContext(this);
220     }
221 
222     /**
223      * Get address header (including protocol, host, port, user)
224      * but without file path.
225      *
226      @return address header
227      */
228     public String getHeader() {
229         return header;
230     }
231 
232     /**
233      * Get address path (without protocol, host, port and file name).
234      *
235      @return module path
236      */
237     public String getPath() {
238         return path;
239     }
240 
241     /**
242      * Get module file name.
243      *
244      @return  Module file name.
245      */
246     public String getFileName() {
247         return fileName;
248     }
249 
250     /**
251      * Get name of module (file name without <code>.xml</code>).
252      *
253      @return  Module name.
254      */
255     public String getName() {
256         return this.name;
257     }
258 
259     /**
260      * Get fully qualified URL of module.
261      *
262      @return  URL for QEDEQ module.
263      */
264     public String getUrl() {
265         return this.url;
266     }
267 
268     /**
269      * Was this module address created relatively?
270      *
271      @return  Relatively created?
272      */
273     public boolean isRelativeAddress() {
274         return this.relativeAddress;
275     }
276 
277     /**
278      * Is this a local QEDEQ file. That means the address starts with <code>file:</code>.
279      *
280      @return  Is the QEDEQ module a local file?
281      */
282     public boolean isFileAddress() {
283         return fileAddress;
284     }
285 
286     public final String toString() {
287         return url;
288     }
289 
290     public final int hashCode() {
291         return url.hashCode();
292     }
293 
294     // FIXME m31 20100820: what if we have "hoho/hello/sample.xml" and "hoho/hello/../hello/sample.xml"
295     public final boolean equals(final Object object) {
296         if (object == null || !(object instanceof DefaultModuleAddress)) {
297             return false;
298         }
299         return url.equals(((DefaultModuleAddressobject).url);
300     }
301 
302     /**
303      * Get the file name of the specified module.
304      *
305      @param   spec    Here are the (perhaps relative) addresses to
306      *                  another module.
307      @return  File name of specified module.
308      */
309     private static final String getModuleFileName(final Specification spec) {
310 
311         return spec.getName() ".xml";
312     }
313 
314     public final ModuleAddress[] getModulePaths(final Specification specthrows IOException {
315 
316         final String fileNameEnd = getModuleFileName(spec);
317         final LocationList locations = spec.getLocationList();
318         final ModuleAddress[] result
319             new ModuleAddress[(locations != null ? locations.size() 0)];
320         for (int i = 0; i < result.length; i++) {
321             String file = locations.get(i).getLocation();
322             if (file.equals(".")) {
323                 file = "";
324             else if (!file.endsWith("/")) {
325                 file += "/";
326             }
327             file += fileNameEnd;
328             result[inew DefaultModuleAddress(file, this);
329         }
330         return result;
331     }
332 
333     /**
334      * Create relative address from <code>orgin</code> to <code>next</code>.
335      *
336      @param   origin  This is the original location (URL!).
337      @param   next    This should be the next location (URL!).
338      @return  Relative (or if necessary absolute) address.
339      */
340     public static final String createRelativeAddress(final String origin,
341             final String next) {
342         if (origin.equals(next)) {
343             return "";
344         }
345         try {
346             final URL urlOrgin = new URL(origin);
347             final URL urlNext = new URL(next);
348 
349             if (urlOrgin.getProtocol().equals(urlNext.getProtocol())
350                     && urlOrgin.getHost().equals(urlNext.getHost())
351                     && urlOrgin.getPort() == urlNext.getPort()) {
352                 final String org = urlOrgin.getFile();
353                 final String nex = urlNext.getFile();
354                 int i = -1// position of next '/'
355                 int j = 0;  // position of last '/'
356                 while (<= (i = org.indexOf("/", j))) {
357                     if (i >= && nex.length() > i
358                             && org.substring(j, i).equals(
359                             nex.substring(j, i))) {
360                         j = i + 1;
361                     else {
362                         break;
363                     }
364                 }
365                 if (j > 0) {
366                     i = j;
367                     StringBuffer result = new StringBuffer(nex.length());
368                     while (<= (i = org.indexOf("/", i))) {
369                         i++;
370                         result.append("../");
371                     }
372                     result.append(nex.substring(j));
373                     return result.toString();
374                 else {
375                     return "/" + nex;
376                 }
377             else {    // no relative address possible
378                 return urlNext.toString();
379             }
380         catch (MalformedURLException e) {
381             return next;
382         }
383 
384     }
385 
386 
387 }