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