001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028package edu.wisc.ssec.mcidasv.startupmanager;
029
030import java.io.File;
031import java.nio.file.Files;
032import java.nio.file.Path;
033import java.nio.file.Paths;
034import java.util.Objects;
035
036import edu.wisc.ssec.mcidasv.Constants;
037import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster;
038
039/**
040 * Represents platform specific details used by McIDAS-V. In particular, there
041 * are useful methods related to the McIDAS-V {@literal "userpath"}.
042 *
043 * <p>Currently McIDAS-V distinguishes between {@literal "Unix-like"}, macOS,
044 * and {@literal "Windows"}; these can be accessed using
045 * {@code Platform.UNIXLIKE}, {@code Platform.WINDOWS},
046 * or {@code Platform.MAC}.</p>
047 */
048public enum Platform {
049    /** Instance of unix-specific platform information. */
050    UNIXLIKE("runMcV.prefs", "\n"),
051
052    /** macOS-specicfic platform information. */
053    MAC("runMcV.prefs", "\n"),
054
055    /** Instance of windows-specific platform information. */
056    WINDOWS("runMcV-Prefs.bat", "\r\n");
057
058    /** Path to the user's {@literal "userpath"} directory. */
059    private String userDirectory;
060    
061    /** The path to the user's copy of the startup preferences. */
062    private String userPrefs;
063    
064    /** Path to the preference file that ships with McIDAS-V. */
065    private final String defaultPrefs;
066    
067    /** Holds the platform's representation of a new line. */
068    private final String newLine;
069
070    /** Path to the bundles subdirectory within {@code userDirectory}. */
071    private final String userBundles;
072    
073    /** Total amount of memory avilable in megabytes */
074    private int availableMemory = 0;
075    
076    /**
077     * Initializes the platform-specific paths to the different files 
078     * required by the startup manager.
079     * 
080     * @param defaultPrefs Path to the preferences file that ships with
081     * McIDAS-V. Cannot be {@code null} or empty.
082     * @param newLine Character(s!) that represent a new line for this 
083     * platform. Cannot be {@code null} or empty.
084     * 
085     * @throws NullPointerException if either {@code defaultPrefs} or
086     * {@code newLine} are {@code null}.
087     * 
088     * @throws IllegalArgumentException if either {@code defaultPrefs} or
089     * {@code newLine} are an empty string.
090     */
091    Platform(final String defaultPrefs, final String newLine) {
092        Objects.requireNonNull(defaultPrefs);
093        Objects.requireNonNull(newLine);
094        if (defaultPrefs.isEmpty() || newLine.isEmpty()) {
095            throw new IllegalArgumentException("");
096        }
097
098        String osName = System.getProperty("os.name");
099        Path tmpPath;
100        if (osName.startsWith("Mac OS X")) {
101            tmpPath = Paths.get(System.getProperty("user.home"), "Documents", Constants.USER_DIRECTORY_NAME);
102        } else {
103            tmpPath = Paths.get(System.getProperty("user.home"), Constants.USER_DIRECTORY_NAME);
104        }
105
106        this.userDirectory = tmpPath.toString();
107        this.userPrefs = Paths.get(userDirectory, defaultPrefs).toString();
108        this.defaultPrefs = defaultPrefs;
109        this.newLine = newLine;
110        this.userBundles = Paths.get(this.userDirectory, "bundles").toString();
111    }
112    
113    /**
114     * Sets the path to the user's {@literal "userpath"} directory explicitly.
115     * If the specified path does not yet exist, this method will first
116     * attempt to create it. The method will then attempt to verify whether or
117     * not McIDAS-V can use the path.
118     * 
119     * @param path New path. Cannot be {@code null}, but does not have to exist
120     * prior to running this method. Be aware that this method will attempt to
121     * create {@code path} if it does not already exist.
122     *
123     * @throws IllegalArgumentException if {@code path} is not a
124     * directory, or if it not both readable and writable.
125     */
126    public void setUserDirectory(final String path) throws IllegalArgumentException {
127        File tmp = new File(path);
128        if (!tmp.exists()) {
129            tmp.mkdir();
130        }
131
132        // TODO(jon): or would tmp.isFile() suffice?
133        if (tmp.exists() && !tmp.isDirectory()) {
134            throw new IllegalArgumentException('\'' +path+"' is not a directory.");
135        }
136
137        Path p = tmp.toPath();
138        boolean canRead = Files.isReadable(p);
139        boolean canWrite = Files.isWritable(p);
140
141        if (!canRead && !canWrite) {
142            throw new IllegalArgumentException('\''+path+"' must be both readable and writable by McIDAS-V.");
143        } else if (!canRead) {
144            throw new IllegalArgumentException('\''+path+"' must be readable by McIDAS-V.");
145        } else if (!canWrite) {
146            throw new IllegalArgumentException('\''+path+"' must be writable by McIDAS-V.");
147        }
148
149        userDirectory = path;
150        userPrefs = Paths.get(userDirectory, defaultPrefs).toString();
151    }
152    
153    /**
154     * Sets the amount of available memory. {@code megabytes} must be 
155     * greater than or equal to zero.
156     * 
157     * @param megabytes Memory in megabytes.
158     * 
159     * @throws NullPointerException if {@code megabytes} is {@code null}.
160     * @throws IllegalArgumentException if {@code megabytes} is less than
161     * zero or does not represent an integer.
162     * 
163     * @see StartupManager#getArgs
164     *
165     * @deprecated There's not really a need for this method; the JVM can
166     *             tell us the amount of memory.
167     */
168    public void setAvailableMemory(String megabytes) {
169        Objects.requireNonNull(megabytes, "Available memory cannot be null");
170        if (megabytes.isEmpty()) {
171            megabytes = "0";
172        }
173        
174        try {
175            int test = Integer.parseInt(megabytes);
176            if (test < 0) {
177                throw new IllegalArgumentException("Available memory must be a non-negative integer, not \""+megabytes+"\"");
178            }
179            availableMemory = test;
180        } catch (NumberFormatException e) {
181            throw new IllegalArgumentException("Could not convert \""+megabytes+"\" to a non-negative integer", e);
182        }
183    }
184    
185    /**
186     * Returns the path to the user's {@literal "userpath"} directory.
187     * 
188     * @return Path to the user's directory.
189     */
190    public String getUserDirectory() {
191        return userDirectory;
192    }
193    
194    /**
195     * Returns the path to a file in the user's {@literal "userpath"} directory.
196     * 
197     * @param filename Filename within the {@code userpath}. Cannot be 
198     * {@code null}, but does not need to be a filename that already exists 
199     * within the {@code userpath}.
200     * 
201     * @return Path to a file in the user's directory. <b>Note:</b> the file 
202     * may not yet exist.
203     */
204    public String getUserFile(String filename) {
205        return Paths.get(userDirectory, filename).toString();
206    }
207
208    /**
209     * Returns the path to the user's bundles directory. Note: this should be
210     * a directory within {@link #getUserDirectory()}.
211     *
212     * @return Path to the user's bundles directory.
213     */
214    public String getUserBundles() {
215        return userBundles;
216    }
217    
218    /**
219     * Returns the amount of available memory in megabytes.
220     * 
221     * @return Available memory in megabytes.
222     */
223    public int getAvailableMemory() {
224        return availableMemory;
225    }
226    
227    /**
228     * Returns the path of user's copy of the startup preferences.
229     * 
230     * @return Path to the user's startup preferences file.
231     */
232    public String getUserPrefs() {
233        return userPrefs;
234    }
235    
236    /**
237     * Returns the path of the startup preferences included in the McIDAS-V
238     * distribution. Mostly useful for normalizing the user directory.
239     * 
240     * @return Path to the default startup preferences.
241     * 
242     * @see OptionMaster#normalizeUserDirectory()
243     */
244    public String getDefaultPrefs() {
245        return defaultPrefs;
246    }
247    
248    /**
249     * Returns the platform's notion of a new line.
250     * 
251     * @return Unix-like: {@literal \n}; Windows: {@literal \r\n}.
252     */
253    public String getNewLine() {
254        return newLine;
255    }
256    
257    /**
258     * Returns a brief summary of the platform specific file locations. 
259     * Please note that the format and contents are subject to change.
260     * 
261     * @return String that looks like 
262     * {@code [Platform@HASHCODE: defaultPrefs=..., userDirectory=..., 
263     * userPrefs=...]}
264     */
265    @Override public String toString() {
266        return String.format(
267            "[Platform@%x: defaultPrefs=%s, userDirectory=%s, userPrefs=%s]",
268            hashCode(), defaultPrefs, userDirectory, userPrefs);
269    }
270}