001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences;
003
004import java.io.File;
005import java.util.Objects;
006import java.util.regex.Matcher;
007import java.util.regex.Pattern;
008
009import org.openstreetmap.josm.Main;
010
011/**
012 * A source entry primarily used to save the user's selection of mappaint styles,
013 * but also for preset sources or validator rules.
014 * @since 3796
015 */
016public class SourceEntry {
017
018    /**
019     *  A URL can be anything that CachedFile understands, i.e.
020     *  a local file, http://, or a file from the current jar
021     */
022    public String url;
023
024    /**
025     * Indicates, that {@link #url} is a zip file and the resource is
026     * inside the zip file.
027     */
028    public boolean isZip;
029
030    /**
031     * If {@link #isZip} is true, denotes the path inside the zip file.
032     */
033    public String zipEntryPath;
034
035    /**
036     *  Name is used as a namespace for color preferences and (currently) only
037     *  one file with a name can be loaded at a time. Additional styles must
038     *  either have the same name as the main style or no name at all.
039     *  If no name is provided, it will be set to the default value "standard".
040     *  The name can also be given in the xml file as attribute for the rules tag.
041     *  (This overrides the name given in the preferences, otherwise both
042     *  methods are equivalent.)
043     */
044    public String name;
045
046    /**
047     * A title that can be used as menu entry.
048     */
049    public String title;
050
051    /**
052     * active is a boolean flag that can be used to turn the source on or off at runtime.
053     */
054    public boolean active;
055
056    /**
057     * Constructs a new {@code SourceEntry}.
058     * @param url URL that {@link org.openstreetmap.josm.io.CachedFile} understands
059     * @param isZip if url is a zip file and the resource is inside the zip file
060     * @param zipEntryPath If {@code isZip} is {@code true}, denotes the path inside the zip file
061     * @param name Source name
062     * @param title title that can be used as menu entry
063     * @param active boolean flag that can be used to turn the source on or off at runtime
064     * @see #url
065     * @see #isZip
066     * @see #zipEntryPath
067     * @see #name
068     * @see #title
069     * @see #active
070     */
071    public SourceEntry(String url, boolean isZip, String zipEntryPath, String name, String title, boolean active) {
072        this.url = url;
073        this.isZip = isZip;
074        this.zipEntryPath = "".equals(zipEntryPath) ? null : zipEntryPath;
075        this.name = "".equals(name) ? null : name;
076        this.title = "".equals(title) ? null : title;
077        this.active = active;
078    }
079
080    /**
081     * Constructs a new {@code SourceEntry}.
082     * @param url URL that {@link org.openstreetmap.josm.io.CachedFile} understands
083     * @param name Source name
084     * @param title title that can be used as menu entry
085     * @param active boolean flag that can be used to turn the source on or off at runtime
086     * @see #url
087     * @see #name
088     * @see #title
089     * @see #active
090     */
091    public SourceEntry(String url, String name, String title, Boolean active) {
092        this(url, false, null, name, title, active);
093    }
094
095    /**
096     * Constructs a new {@code SourceEntry}.
097     * @param e existing source entry to copy
098     */
099    public SourceEntry(SourceEntry e) {
100        this.url = e.url;
101        this.isZip = e.isZip;
102        this.zipEntryPath = e.zipEntryPath;
103        this.name = e.name;
104        this.title = e.title;
105        this.active = e.active;
106    }
107
108    @Override
109    public boolean equals(Object obj) {
110        if (obj == null || getClass() != obj.getClass())
111            return false;
112        final SourceEntry other = (SourceEntry) obj;
113        return Objects.equals(other.url, url) &&
114                other.isZip == isZip &&
115                Objects.equals(other.zipEntryPath, zipEntryPath) &&
116                Objects.equals(other.name, name) &&
117                Objects.equals(other.title, title) &&
118                other.active == active;
119    }
120
121    @Override
122    public int hashCode() {
123        int hash = 5;
124        hash = 89 * hash + (this.url != null ? this.url.hashCode() : 0);
125        hash = 89 * hash + (this.isZip ? 1 : 0);
126        hash = 89 * hash + (this.zipEntryPath != null ? this.zipEntryPath.hashCode() : 0);
127        hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0);
128        hash = 89 * hash + (this.title != null ? this.title.hashCode() : 0);
129        hash = 89 * hash + (this.active ? 1 : 0);
130        return hash;
131    }
132
133    @Override
134    public String toString() {
135        return title != null ? title : url;
136    }
137
138    /**
139     * String to show in menus and error messages.
140     * @return Usually the shortdescription, but can be the file name
141     * if no shortdescription is available.
142     */
143    public String getDisplayString() {
144        if (title != null)
145            return title;
146        else
147            return getFileNamePart();
148    }
149
150    /**
151     * Extracts file part from url, e.g.:
152     * <code>http://www.test.com/file.xml?format=text --&gt; file.xml</code>
153     * @return The filename part of the URL
154     */
155    public String getFileNamePart() {
156        Pattern p = Pattern.compile("([^/\\\\]*?)([?].*)?$");
157        Matcher m = p.matcher(url);
158        if (m.find()) {
159            return m.group(1);
160        } else {
161            Main.warn("Unexpected URL format: "+url);
162            return url;
163        }
164    }
165
166    /**
167     * the name / identifier that should be used to save custom color values
168     * and similar stuff to the preference file
169     * @return the identifier; never null. Usually the result is "standard"
170     */
171    public String getPrefName() {
172        return name == null ? "standard" : name;
173    }
174
175    /**
176     * Determines if this source denotes a file on a local filesystem.
177     * @return {@code true} if the source is a local file
178     */
179    public boolean isLocal() {
180        if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("resource://"))
181            return false;
182        return true;
183    }
184
185    /**
186     * Return the source directory, only for local files.
187     * @return The source directory, or {@code null} if this file isn't local, or does not have a parent
188     * @since 7276
189     */
190    public File getLocalSourceDir() {
191        if (!isLocal())
192            return null;
193        return new File(url).getParentFile();
194    }
195
196    /**
197     * Returns the parent directory of the resource inside the zip file.
198     *
199     * @return the parent directory of the resource inside the zip file,
200     * "." if zipEntryPath is a top level file; null, if zipEntryPath is null
201     */
202    public String getZipEntryDirName() {
203        if (zipEntryPath == null) return null;
204        File file = new File(zipEntryPath);
205        File dir = file.getParentFile();
206        if (dir == null) return ".";
207        String path = dir.getPath();
208        if (!"/".equals(File.separator)) {
209            path = path.replace(File.separator, "/");
210        }
211        return path;
212    }
213}