001package net.sf.cpsolver.ifs.util;
002
003import java.io.ByteArrayOutputStream;
004import java.io.File;
005import java.io.FileInputStream;
006import java.io.IOException;
007import java.io.OutputStream;
008import java.io.PrintStream;
009import java.util.ArrayList;
010import java.util.Collection;
011import java.util.Date;
012import java.util.Iterator;
013import java.util.List;
014import java.util.Map;
015import java.util.Properties;
016import java.util.Random;
017import java.util.StringTokenizer;
018import java.util.TreeSet;
019
020import org.apache.log4j.Level;
021import org.apache.log4j.Logger;
022import org.apache.log4j.PropertyConfigurator;
023
024/**
025 * Several auxiliary static methods.
026 * 
027 * @version IFS 1.2 (Iterative Forward Search)<br>
028 *          Copyright (C) 2006 - 2010 Tomáš Müller<br>
029 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
030 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
031 * <br>
032 *          This library is free software; you can redistribute it and/or modify
033 *          it under the terms of the GNU Lesser General Public License as
034 *          published by the Free Software Foundation; either version 3 of the
035 *          License, or (at your option) any later version. <br>
036 * <br>
037 *          This library is distributed in the hope that it will be useful, but
038 *          WITHOUT ANY WARRANTY; without even the implied warranty of
039 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
040 *          Lesser General Public License for more details. <br>
041 * <br>
042 *          You should have received a copy of the GNU Lesser General Public
043 *          License along with this library; if not see
044 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
045 */
046public class ToolBox {
047    private static long sSeed = System.currentTimeMillis();
048    private static Random sRandom = new Random(sSeed);
049
050    /** Returns random number (int) from the set 0 .. limit - 1 */
051    public static int random(int limit) {
052        return (int) (random() * limit);
053    }
054
055    /** Returns random element from the given set of elements */
056    public static <E> E random(Collection<E> set) {
057        switch (set == null ? 0 : set.size()) {
058            case 0:
059                return null;
060            case 1:
061                return set.iterator().next();
062            case 2:
063                Iterator<E> i = set.iterator();
064                if (sRandom.nextBoolean()) i.next();
065                return i.next();
066            default:
067                List<E> v = (set instanceof List<?> ? (List<E>) set : new ArrayList<E>(set));
068                return v.get(random(v.size()));
069        }
070    }
071
072    /**
073     * Returns a randomly generated subset of the given set
074     * 
075     * @param set
076     *            set
077     * @param part
078     *            probability of selection of an element into the resultant
079     *            subset
080     */
081    public static <E> Collection<E> subSet(Collection<E> set, double part) {
082        return subSet(set, part, 1);
083    }
084
085    /** Swaps two elements in the list */
086    private static <E> void swap(List<E> list, int first, int second) {
087        E o = list.get(first);
088        list.set(first, list.get(second));
089        list.set(second, o);
090    }
091
092    /**
093     * Returns a randomly generated subset of the given set
094     * 
095     * @param set
096     *            set
097     * @param part
098     *            probability of selection of an element into the resultant
099     *            subset
100     * @param minSize
101     *            minimal size of the returned subset
102     */
103    public static <E> Collection<E> subSet(Collection<E> set, double part, int minSize) {
104        if (set.size() <= minSize || part >= 1.0)
105            return set;
106        ArrayList<E> subSet = new ArrayList<E>(set);
107        int size = set.size();
108        int numberToSelect = Math.max(minSize, (int) (part * set.size()));
109        for (int idx = 0; idx < numberToSelect; idx++) {
110            swap(subSet, idx, idx + (int) (random() * (size - idx)));
111        }
112        return subSet.subList(0, numberToSelect);
113    }
114
115    /** Trim a string to have given length */
116    public static String trim(String s, int length) {
117        if (s.length() > length)
118            return s.substring(0, length);
119        StringBuffer sb = new StringBuffer(s);
120        while (sb.length() < length)
121            sb.append(" ");
122        return sb.toString();
123    }
124
125    /** Multiline representation of a colection */
126    public static String col2string(Collection<?> col, int tab) {
127        StringBuffer tabsb = new StringBuffer();
128        while (tabsb.length() < 2 * tab)
129            tabsb.append("  ");
130        StringBuffer sb = new StringBuffer("[\n");
131        for (Iterator<?> i = col.iterator(); i.hasNext();) {
132            sb.append(tabsb + "  " + i.next() + (i.hasNext() ? "," : "") + "\n");
133        }
134        sb.append(tabsb + "]");
135        return sb.toString();
136    }
137
138    /** Multiline representation of a dictionary */
139    public static <K, V> String dict2string(Map<K, V> dict, int tab) {
140        StringBuffer tabsb = new StringBuffer();
141        while (tabsb.length() < 2 * tab)
142            tabsb.append("  ");
143        StringBuffer sb = new StringBuffer("[\n");
144        TreeSet<K> keys = new TreeSet<K>(dict.keySet());
145        for (K key : keys) {
146            V value = dict.get(key);
147            sb.append(tabsb + "  " + key + ": " + value + "\n");
148        }
149        sb.append(tabsb + "]");
150        return sb.toString();
151    }
152
153    /**
154     * Root mean square
155     * 
156     * @param n
157     *            number of tests
158     * @param x
159     *            total value of all tests
160     * @param x2
161     *            total value^2 of all tests
162     */
163    public static double rms(int n, double x, double x2) {
164        double var = x2 / n;
165        double mean = x / n;
166        return Math.sqrt(Math.abs(var - mean * mean));
167    }
168
169    /** Merge source with target */
170    public static <E> void merge(List<E> target, Collection<E> source) {
171        for (E o : source) {
172            if (!target.contains(o))
173                target.add(o);
174        }
175    }
176
177    /** Returns intersection of two collections */
178    public static <E> List<E> intersect(Collection<E> source1, Collection<E> source2) {
179        List<E> target = new ArrayList<E>();
180        for (E o : source1) {
181            if (!source2.contains(o))
182                target.add(o);
183        }
184        return target;
185    }
186
187    /**
188     * Sets seeds for {@link ToolBox#getRandom()} and {@link ToolBox#random()}
189     * methods.
190     */
191    public static void setSeed(long seed) {
192        sSeed = seed;
193        sRandom = new Random(sSeed);
194    }
195
196    /** Gets current seed */
197    public static long getSeed() {
198        return sSeed;
199    }
200
201    /** Gets random number generator */
202    public static Random getRandom() {
203        return sRandom;
204    }
205
206    /** Generates random double number */
207    public static double random() {
208        return sRandom.nextDouble();
209    }
210
211    /** Configurates log4j loging */
212    public static void configureLogging() {
213        Properties props = new Properties();
214        props.setProperty("log4j.rootLogger", "DEBUG, A1");
215        props.setProperty("log4j.appender.A1", "org.apache.log4j.ConsoleAppender");
216        props.setProperty("log4j.appender.A1.layout", "org.apache.log4j.PatternLayout");
217        props.setProperty("log4j.appender.A1.layout.ConversionPattern", "%-5p %c{2}: %m%n");
218        props.setProperty("log4j.logger.net", "INFO");
219        props.setProperty("log4j.logger.net.sf.cpsolver", "DEBUG");
220        props.setProperty("log4j.logger.org", "INFO");
221        PropertyConfigurator.configure(props);
222    }
223
224    /**
225     * Configurates log4j loging
226     * 
227     * @param logDir
228     *            output folder
229     * @param properties
230     *            some other log4j properties
231     */
232    public static String configureLogging(String logDir, Properties properties) {
233        return configureLogging(logDir, properties, false);
234    }
235
236    public static String configureLogging(String logDir, Properties properties, boolean timeInFileName) {
237        return configureLogging(logDir, properties, timeInFileName, true);
238    }
239
240    /**
241     * Configurates log4j loging
242     * 
243     * @param logDir
244     *            output folder
245     * @param properties
246     *            some other log4j properties
247     * @param timeInFileName
248     *            if true log file is named debug_yyyy-MM-dd_(HH.mm.ss).log, it
249     *            is named debug.log otherwise
250     */
251    public static String configureLogging(String logDir, Properties properties, boolean timeInFileName,
252            boolean includeSystemOuts) {
253        String time = new java.text.SimpleDateFormat("yyyy-MM-dd_(HH.mm.ss)", java.util.Locale.US).format(new Date());
254        (new File(logDir)).mkdirs();
255        String fileName = logDir + File.separator + (timeInFileName ? "debug_" + time : "debug") + ".log";
256        Properties props = (properties != null ? properties : new Properties());
257        if (!props.containsKey("log4j.rootLogger")) {
258            props.setProperty("log4j.rootLogger", "debug, LogFile");
259            if (timeInFileName)
260                props.setProperty("log4j.appender.LogFile", "org.apache.log4j.FileAppender");
261            else {
262                props.setProperty("log4j.appender.LogFile", "org.apache.log4j.DailyRollingFileAppender");
263                props.setProperty("log4j.appender.LogFile.DatePattern", "'.'yyyy-MM-dd");
264            }
265            props.setProperty("log4j.appender.LogFile.File", fileName);
266            props.setProperty("log4j.appender.LogFile.layout", "org.apache.log4j.PatternLayout");
267            props.setProperty("log4j.appender.LogFile.layout.ConversionPattern",
268                    "%d{dd-MMM-yy HH:mm:ss.SSS} [%t] %-5p %c{2}> %m%n");
269        }
270        PropertyConfigurator.configure(props);
271        Logger log = Logger.getRootLogger();
272        log.info("-----------------------------------------------------------------------");
273        log.info("IFS debug file");
274        log.info("");
275        log.info("Created: " + new Date());
276        log.info("");
277        log.info("System info:");
278        log.info("System:      " + System.getProperty("os.name") + " " + System.getProperty("os.version") + " "
279                + System.getProperty("os.arch"));
280        log.info("CPU:         " + System.getProperty("sun.cpu.isalist") + " endian:"
281                + System.getProperty("sun.cpu.endian") + " encoding:" + System.getProperty("sun.io.unicode.encoding"));
282        log.info("Java:        " + System.getProperty("java.vendor") + ", " + System.getProperty("java.runtime.name")
283                + " " + System.getProperty("java.runtime.version", System.getProperty("java.version")));
284        log.info("User:        " + System.getProperty("user.name"));
285        log.info("Timezone:    " + System.getProperty("user.timezone"));
286        log.info("Working dir: " + System.getProperty("user.dir"));
287        log.info("Classpath:   " + System.getProperty("java.class.path"));
288        log.info("");
289        if (includeSystemOuts) {
290            System.setErr(new PrintStream(new LogOutputStream(System.err, Logger.getLogger("STDERR"), Level.ERROR)));
291            System.setOut(new PrintStream(new LogOutputStream(System.out, Logger.getLogger("STDOUT"), Level.DEBUG)));
292        }
293        return fileName;
294    }
295
296    /**
297     * Loads data properties. If there is INCLUDE property available, it is
298     * interpreted as semi-colon separated list of porperty files which should
299     * be also loaded (works recursively).
300     * 
301     */
302    public static DataProperties loadProperties(File propertyFile) {
303        FileInputStream is = null;
304        try {
305            DataProperties ret = new DataProperties();
306            is = new FileInputStream(propertyFile);
307            ret.load(is);
308            is.close();
309            is = null;
310            if (ret.getProperty("INCLUDE") != null) {
311
312                StringTokenizer stk = new StringTokenizer(ret.getProperty("INCLUDE"), ";");
313                while (stk.hasMoreTokens()) {
314                    String aFile = stk.nextToken();
315                    System.out.println("  Loading included file '" + aFile + "' ... ");
316                    if ((new File(aFile)).exists())
317                        is = new FileInputStream(aFile);
318                    if ((new File(propertyFile.getParent() + File.separator + aFile)).exists())
319                        is = new FileInputStream(propertyFile.getParent() + File.separator + aFile);
320                    if (is == null)
321                        System.err.println("Unable to find include file '" + aFile + "'.");
322                    ret.load(is);
323                    is.close();
324                    is = null;
325                }
326                ret.remove("INCLUDE");
327            }
328            return ret;
329        } catch (Exception e) {
330            System.err.println("Unable to load property file " + propertyFile);
331            e.printStackTrace();
332            return new DataProperties();
333        } finally {
334            try {
335                if (is != null)
336                    is.close();
337            } catch (IOException e) {
338            }
339        }
340    }
341
342    public static boolean equals(Object o1, Object o2) {
343        return (o1 == null ? o2 == null : o1.equals(o2));
344    }
345
346    private static class LogOutputStream extends OutputStream {
347        private Logger iLogger = null;
348        private Level iLevel = null;
349        private OutputStream iOldOutputStream;
350        private ByteArrayOutputStream iOut = new ByteArrayOutputStream();
351
352        public LogOutputStream(OutputStream oldOutputStream, Logger logger, Level level) {
353            iLogger = logger;
354            iLevel = level;
355            iOldOutputStream = oldOutputStream;
356        }
357
358        @Override
359        public void write(int b) throws IOException {
360            iOldOutputStream.write(b);
361            if (b == '\r')
362                return;
363            if (b == '\n') {
364                iOut.flush();
365                iLogger.log(iLevel, new String(iOut.toByteArray()));
366                iOut.reset();
367            } else
368                iOut.write(b);
369        }
370    }
371    
372    public static <E> List<E> toList(E... obj) {
373        List<E> ret = new ArrayList<E>(obj == null ? 0 : obj.length);
374        if (obj != null)
375            for (E e: obj)
376                ret.add(e);
377        return ret;
378    }
379}