Checks and creates dirs at startup, purges 'res' directory.
Moved some of the startup settings from NeverNote and Global into new
StartupConfig class for better separation of things that can't be changed
after startup.
-#Thu Jul 15 16:56:27 NZST 2010
+#Tue Jul 20 01:42:00 NZST 2010
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-#Mon Feb 01 13:37:03 EST 2010\r
-eclipse.preferences.version=1\r
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true\r
-sp_cleanup.add_default_serial_version_id=true\r
-sp_cleanup.add_generated_serial_version_id=false\r
-sp_cleanup.add_missing_annotations=true\r
-sp_cleanup.add_missing_deprecated_annotations=true\r
-sp_cleanup.add_missing_methods=false\r
-sp_cleanup.add_missing_nls_tags=false\r
-sp_cleanup.add_missing_override_annotations=true\r
-sp_cleanup.add_serial_version_id=false\r
-sp_cleanup.always_use_blocks=true\r
-sp_cleanup.always_use_parentheses_in_expressions=false\r
-sp_cleanup.always_use_this_for_non_static_field_access=false\r
-sp_cleanup.always_use_this_for_non_static_method_access=false\r
-sp_cleanup.convert_to_enhanced_for_loop=true\r
-sp_cleanup.correct_indentation=false\r
-sp_cleanup.format_source_code=false\r
-sp_cleanup.format_source_code_changes_only=false\r
-sp_cleanup.make_local_variable_final=false\r
-sp_cleanup.make_parameters_final=false\r
-sp_cleanup.make_private_fields_final=true\r
-sp_cleanup.make_type_abstract_if_missing_method=false\r
-sp_cleanup.make_variable_declarations_final=true\r
-sp_cleanup.never_use_blocks=false\r
-sp_cleanup.never_use_parentheses_in_expressions=true\r
-sp_cleanup.on_save_use_additional_actions=true\r
-sp_cleanup.organize_imports=true\r
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false\r
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true\r
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true\r
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false\r
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false\r
-sp_cleanup.remove_private_constructors=true\r
-sp_cleanup.remove_trailing_whitespaces=false\r
-sp_cleanup.remove_trailing_whitespaces_all=true\r
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false\r
-sp_cleanup.remove_unnecessary_casts=true\r
-sp_cleanup.remove_unnecessary_nls_tags=false\r
-sp_cleanup.remove_unused_imports=false\r
-sp_cleanup.remove_unused_local_variables=false\r
-sp_cleanup.remove_unused_private_fields=true\r
-sp_cleanup.remove_unused_private_members=false\r
-sp_cleanup.remove_unused_private_methods=true\r
-sp_cleanup.remove_unused_private_types=true\r
-sp_cleanup.sort_members=false\r
-sp_cleanup.sort_members_all=false\r
-sp_cleanup.use_blocks=false\r
-sp_cleanup.use_blocks_only_for_return_and_throw=false\r
-sp_cleanup.use_parentheses_in_expressions=false\r
-sp_cleanup.use_this_for_non_static_field_access=false\r
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true\r
-sp_cleanup.use_this_for_non_static_method_access=false\r
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true\r
+#Tue Jul 20 01:42:00 NZST 2010
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_settings_version=11
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_to_enhanced_for_loop=true
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=false
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.make_local_variable_final=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_trailing_whitespaces=false
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
import com.trolltech.qt.core.QSettings;\r
import com.trolltech.qt.gui.QPalette;\r
\r
+import cx.fbn.nevernote.config.FileManager;\r
+import cx.fbn.nevernote.config.InitializationException;\r
+import cx.fbn.nevernote.config.StartupConfig;\r
import cx.fbn.nevernote.gui.ContainsAttributeFilterTable;\r
import cx.fbn.nevernote.gui.DateAttributeFilterTable;\r
import cx.fbn.nevernote.gui.ShortcutKeys;\r
private static String wordRegex;\r
public static boolean enableCarriageReturnFix = false;\r
\r
- public static String name = null;\r
public static QSettings settings;\r
public static boolean isConnected;\r
public static boolean showDeleted = false;\r
PrintStream stdoutStream;\r
public static QPalette originalPalette;\r
public static ShortcutKeys shortcutKeys;\r
- public static boolean disableViewing = false;\r
+ private static boolean disableViewing;\r
\r
public static List<String> invalidElements = new ArrayList<String>();\r
public static HashMap<String, ArrayList<String>> invalidAttributes = new HashMap<String, ArrayList<String>>();\r
\r
static Calendar startTraceTime;\r
static Calendar intervalTraceTime;\r
- \r
+\r
+ private static FileManager fileManager;\r
+\r
// Do initial setup \r
- public static void setup() {\r
- if (name == null)\r
- name = "NeverNote";\r
- settings = new QSettings("fbn.cx", name);\r
- currentDir = getDirectoryPath();\r
+ public static void setup(StartupConfig startupConfig) throws InitializationException {\r
+ settings = new QSettings("fbn.cx", startupConfig.getName());\r
+ disableViewing = startupConfig.getDisableViewing();\r
+\r
+ fileManager = new FileManager(startupConfig.getHomeDirPath());\r
+ currentDir = fileManager.getHomeDirPath();\r
+\r
getServer();\r
settings.beginGroup("General");\r
String regex = (String) settings.value("regex", "[,\\s]+");\r
resourceMap = new HashMap<String,String>();\r
\r
}\r
- public static void setName(String n) {\r
- if (!n.trim().equals("")) {\r
- name = "NeverNote-"+n;\r
- }\r
- }\r
+\r
public static String getDirectoryPath() {\r
if (currentDir == null) {\r
currentDir = System.getProperty("user.dir");\r
}\r
return currentDir;\r
}\r
- public static void setDirectoryPath(String path) {\r
- if (path.trim().equals(""))\r
- path = System.getProperty("user.dir");\r
- if (!path.substring(path.length()-1).equals(File.separator)) \r
- path = path+File.separator;\r
- currentDir = path;\r
- }\r
+\r
public static String getWordRegex() {\r
return wordRegex;\r
}\r
startTraceTime = null;\r
}\r
\r
+ public static FileManager getFileManager() {\r
+ return fileManager;\r
+ }\r
+\r
+ public static boolean getDisableViewing() {\r
+ return disableViewing;\r
+ }\r
}\r
\r
import com.trolltech.qt.core.QFileInfo;
import com.trolltech.qt.core.QFileSystemWatcher;
import com.trolltech.qt.core.QIODevice;
-import com.trolltech.qt.core.QIODevice.OpenModeFlag;
import com.trolltech.qt.core.QModelIndex;
import com.trolltech.qt.core.QSize;
import com.trolltech.qt.core.QTemporaryFile;
import com.trolltech.qt.core.QTimer;
import com.trolltech.qt.core.QUrl;
import com.trolltech.qt.core.Qt;
+import com.trolltech.qt.core.QIODevice.OpenModeFlag;
import com.trolltech.qt.core.Qt.SortOrder;
import com.trolltech.qt.core.Qt.WidgetAttribute;
import com.trolltech.qt.gui.QAbstractItemView;
-import com.trolltech.qt.gui.QAbstractItemView.ScrollHint;
import com.trolltech.qt.gui.QAction;
import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QCloseEvent;
import com.trolltech.qt.gui.QColor;
import com.trolltech.qt.gui.QComboBox;
-import com.trolltech.qt.gui.QComboBox.InsertPolicy;
import com.trolltech.qt.gui.QCursor;
import com.trolltech.qt.gui.QDesktopServices;
import com.trolltech.qt.gui.QDialog;
import com.trolltech.qt.gui.QFileDialog;
-import com.trolltech.qt.gui.QFileDialog.AcceptMode;
-import com.trolltech.qt.gui.QFileDialog.FileMode;
import com.trolltech.qt.gui.QGridLayout;
import com.trolltech.qt.gui.QHBoxLayout;
import com.trolltech.qt.gui.QIcon;
import com.trolltech.qt.gui.QMainWindow;
import com.trolltech.qt.gui.QMenu;
import com.trolltech.qt.gui.QMessageBox;
-import com.trolltech.qt.gui.QMessageBox.StandardButton;
import com.trolltech.qt.gui.QPixmap;
import com.trolltech.qt.gui.QPrintDialog;
import com.trolltech.qt.gui.QPrinter;
import com.trolltech.qt.gui.QProgressBar;
import com.trolltech.qt.gui.QSizePolicy;
-import com.trolltech.qt.gui.QSizePolicy.Policy;
import com.trolltech.qt.gui.QSpinBox;
import com.trolltech.qt.gui.QSplashScreen;
import com.trolltech.qt.gui.QSplitter;
import com.trolltech.qt.gui.QTextEdit;
import com.trolltech.qt.gui.QToolBar;
import com.trolltech.qt.gui.QTreeWidgetItem;
-import com.trolltech.qt.webkit.QWebPage.WebAction;
+import com.trolltech.qt.gui.QAbstractItemView.ScrollHint;
+import com.trolltech.qt.gui.QComboBox.InsertPolicy;
+import com.trolltech.qt.gui.QFileDialog.AcceptMode;
+import com.trolltech.qt.gui.QFileDialog.FileMode;
+import com.trolltech.qt.gui.QMessageBox.StandardButton;
+import com.trolltech.qt.gui.QSizePolicy.Policy;
import com.trolltech.qt.webkit.QWebSettings;
+import com.trolltech.qt.webkit.QWebPage.WebAction;
import com.trolltech.qt.xml.QDomAttr;
import com.trolltech.qt.xml.QDomDocument;
import com.trolltech.qt.xml.QDomElement;
import com.trolltech.qt.xml.QDomNodeList;
+import cx.fbn.nevernote.config.InitializationException;
+import cx.fbn.nevernote.config.StartupConfig;
import cx.fbn.nevernote.dialog.AccountDialog;
import cx.fbn.nevernote.dialog.ConfigDialog;
import cx.fbn.nevernote.dialog.DatabaseLoginDialog;
QApplication.initialize(args);
QPixmap pixmap = new QPixmap("classpath:cx/fbn/nevernote/icons/splash_logo.png");
QSplashScreen splash = new QSplashScreen(pixmap);
-
- for (String arg : args) {
- String lower = arg.toLowerCase();
- if (lower.startsWith("--name="))
- Global.setName(arg.substring(arg.indexOf('=')+1));
- if (lower.startsWith("--home="))
- Global.currentDir = arg.substring(arg.indexOf('=')+1);
- if (lower.startsWith("--disable-viewing"))
- Global.disableViewing = true;
+
+ try {
+ initializeGlobalSettings(args);
+ } catch (InitializationException e) {
+ QMessageBox.critical(null, "Startup error", "Aborting: " + e.getMessage());
+ return;
}
- Global.setup();
+
boolean showSplash = Global.isWindowVisible("SplashScreen");
if (showSplash)
splash.show();
System.out.println("Goodbye.");
QApplication.exit();
}
- // Exit point
+
+ private static void initializeGlobalSettings(String[] args) throws InitializationException {
+ StartupConfig startupConfig = new StartupConfig();
+
+ for (String arg : args) {
+ String lower = arg.toLowerCase();
+ if (lower.startsWith("--name="))
+ startupConfig.setName(arg.substring(arg.indexOf('=') + 1));
+ if (lower.startsWith("--home="))
+ startupConfig.setHomeDirPath(arg.substring(arg.indexOf('=') + 1));
+ if (lower.startsWith("--disable-viewing"))
+ startupConfig.setDisableViewing(true);
+ }
+
+ Global.setup(startupConfig);
+ }
+
+ // Exit point
@Override
public void closeEvent(QCloseEvent event) {
logger.log(logger.HIGH, "Entering NeverNote.closeEvent");
// Get a note from Evernote (and put it in the browser)
private void refreshEvernoteNote(boolean reload) {
logger.log(logger.HIGH, "Entering NeverNote.refreshEvernoteNote");
- if (Global.disableViewing) {
+ if (Global.getDisableViewing()) {
browserWindow.setEnabled(false);
return;
}
--- /dev/null
+package cx.fbn.nevernote.config;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.regex.Pattern;
+
+/**
+ * Provides access to NeverNote standard runtime directories.
+ *
+ * @author Nick Clarke
+ */
+public class FileManager {
+
+ private static final Pattern ALL_PATH_SEPARATORS_REGEX = Pattern.compile("[/\\\\]");
+
+ private final String homeDirPath;
+ private final File homeDir;
+
+ private final String dbDirPath;
+ private final File dbDir;
+ private final String logsDirPath;
+ private final File logsDir;
+ private final String imagesDirPath;
+ private final File imagesDir;
+ private final String qssDirPath;
+ private final File qssDir;
+ private final String resDirPath;
+ private final File resDir;
+ private final String xmlDirPath;
+ private final File xmlDir;
+
+ /**
+ * Check or create the db, log and res directories, and purge files from 'res' .
+ *
+ * @param homeDirPath the installation dir containing db/log/res directories, must exist
+ */
+ public FileManager(String homeDirPath) throws InitializationException {
+ if (homeDirPath == null) {
+ throw new IllegalArgumentException("homeDirPath must not be null");
+ }
+
+ this.homeDir = new File(toPlatformPathSeparator(homeDirPath));
+ checkExistingWriteableDir(homeDir);
+ this.homeDirPath = slashTerminatePath(homeDir.getPath());
+
+ // Read-only
+ imagesDir = new File(homeDir, "images");
+ checkExistingReadableDir(imagesDir);
+ imagesDirPath = slashTerminatePath(imagesDir.getPath());
+
+ qssDir = new File(homeDir, "qss");
+ checkExistingReadableDir(qssDir);
+ qssDirPath = slashTerminatePath(qssDir.getPath());
+
+ xmlDir = new File(homeDir, "xml");
+ checkExistingReadableDir(xmlDir);
+ xmlDirPath = slashTerminatePath(xmlDir.getPath());
+
+ // Read-write
+ dbDir = new File(homeDir, "db");
+ createDirOrCheckWriteable(dbDir);
+ dbDirPath = slashTerminatePath(dbDir.getPath());
+
+ logsDir = new File(homeDir, "logs");
+ createDirOrCheckWriteable(logsDir);
+ logsDirPath = slashTerminatePath(logsDir.getPath());
+
+ resDir = new File(homeDir, "res");
+ createDirOrCheckWriteable(resDir);
+ resDirPath = slashTerminatePath(resDir.getPath());
+
+ deleteTopLevelFiles(resDir);
+ }
+
+ /**
+ * Get the path to the base installation directory, terminated with {@link File#separator}.
+ * This will contain backslashes on Windows.
+ */
+ public String getHomeDirPath() {
+ return homeDirPath;
+ }
+
+ /**
+ * Get a file below the 'db' directory.
+ */
+ public File getDbDirFile(String relativePath) {
+ return new File(dbDir, toPlatformPathSeparator(relativePath));
+ }
+
+ /**
+ * Get a file below the base installation directory.
+ */
+ public File getHomeDirFile(String relativePath) {
+ return new File(homeDir, toPlatformPathSeparator(relativePath));
+ }
+
+ /**
+ * Get a path below the base installation directory, using native {@link File#separator}.
+ * This will contain backslashes on Windows.
+ */
+ public String getHomeDirPath(String relativePath) {
+ return homeDirPath + toPlatformPathSeparator(relativePath);
+ }
+
+ /**
+ * Get a path below the 'db' directory, using native {@link File#separator}.
+ * This will contain backslashes on Windows.
+ */
+ public String getDbDirPath(String relativePath) {
+ return dbDirPath + toPlatformPathSeparator(relativePath);
+ }
+
+ /**
+ * Get a file below the 'images' directory.
+ */
+ public File getImageDirFile(String relativePath) {
+ return new File(imagesDir, toPlatformPathSeparator(relativePath));
+ }
+
+ /**
+ * Get a path below the 'images' directory, using native {@link File#separator}.
+ * This will contain backslashes on Windows.
+ */
+ public String getImageDirPath(String relativePath) {
+ return imagesDirPath + toPlatformPathSeparator(relativePath);
+ }
+
+ /**
+ * Get a file below the 'logs' directory.
+ */
+ public File getLogsDirFile(String relativePath) {
+ return new File(logsDir, toPlatformPathSeparator(relativePath));
+ }
+
+ /**
+ * Get a path below the 'qss' directory, using native {@link File#separator}.
+ * This will contain backslashes on Windows.
+ */
+ public String getQssDirPath(String relativePath) {
+ return qssDirPath + toPlatformPathSeparator(relativePath);
+ }
+
+ /**
+ * Get a path to the 'res' directory, terminated with native {@link File#separator}.
+ * This will contain backslashes on Windows.
+ */
+ public String getResDirPath() {
+ return resDirPath;
+ }
+
+ /**
+ * Get a path below the 'res' directory, using native {@link File#separator}.
+ * This will contain backslashes on Windows.
+ */
+ public String getResDirPath(String relativePath) {
+ return resDirPath + toPlatformPathSeparator(relativePath);
+ }
+
+ /**
+ * Get a file below the 'xml' directory.
+ */
+ public File getXMLDirFile(String relativePath) {
+ return new File(xmlDir, toPlatformPathSeparator(relativePath));
+ }
+
+ private static String toPlatformPathSeparator(String relativePath) {
+ return ALL_PATH_SEPARATORS_REGEX.matcher(relativePath).replaceAll(File.separator);
+ }
+
+ private static String slashTerminatePath(String path) {
+ if (!path.substring(path.length() - 1).equals(File.separator)) {
+ return path + File.separator;
+ }
+ return path;
+ }
+
+ /**
+ * Delete first-level files (but not directories) from the directory.
+ *
+ * @throws InitializationException for file deletion failures
+ */
+ private static void deleteTopLevelFiles(File dir) throws InitializationException {
+ File[] toDelete = dir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.isFile();
+ }
+ });
+ for (File f : toDelete) {
+ if (!f.delete()) {
+ throw new InitializationException("Failed to delete file: '" + f + "'");
+ }
+ }
+ }
+
+ /**
+ * @throws InitializationException for bad file permissions, or a file instead of a directory
+ */
+ private static void createDirOrCheckWriteable(File dir) throws InitializationException {
+ if (dir.isDirectory()) {
+ // Dir exists, check permissions
+ if (!dir.canRead()) {
+ throw new InitializationException("Directory '" + dir + "' does not have read permission");
+ }
+ if (!dir.canWrite()) {
+ throw new InitializationException("Directory '" + dir + "' does not have write permission");
+ }
+ } else if (!dir.exists()) {
+ if (!dir.mkdirs()) {
+ throw new InitializationException("Failed to create directory '" + dir + "'");
+ }
+ } else {
+ throw new InitializationException("Expected directory '" + dir + "' but found a file instead");
+ }
+ }
+
+ /**
+ * @throws InitializationException if non-existent, bad file permissions, or a file instead of a directory
+ */
+ private static void checkExistingReadableDir(File dir) throws InitializationException {
+ if (dir.isDirectory()) {
+ // Dir exists, check permissions
+ if (!dir.canRead()) {
+ throw new InitializationException("Directory '" + dir + "' does not have read permission");
+ }
+ } else if (!dir.exists()) {
+ throw new InitializationException("Directory '" + dir + "' does not exist");
+ } else {
+ throw new InitializationException("Expected directory '" + dir + "' but found a file instead");
+ }
+ }
+
+ /**
+ * @throws InitializationException if non-existent, bad file permissions, or a file instead of a directory
+ */
+ private static void checkExistingWriteableDir(File dir) throws InitializationException {
+ checkExistingReadableDir(dir);
+ if (!dir.canWrite()) {
+ throw new InitializationException("Directory '" + dir + "' does not have write permission");
+ }
+ }
+}
--- /dev/null
+package cx.fbn.nevernote.config;
+
+/**
+ * Thrown for startup config errors
+ *
+ * @author Nick Clarke
+ */
+public class InitializationException extends Exception {
+ private static final long serialVersionUID = 0L;
+
+ public InitializationException(String message) {
+ super(message);
+ }
+
+}
--- /dev/null
+package cx.fbn.nevernote.config;
+
+
+/**
+ * Things that can only be changed at startup
+ *
+ * @author Nick Clarke
+ */
+public class StartupConfig {
+
+ // Init to default values
+ private String name = "NeverNote";
+ private String homeDirPath = System.getProperty("user.dir");
+ private boolean disableViewing = false;
+
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String n) {
+ if (isNonEmpty(n)) {
+ name = "NeverNote-" + n;
+ }
+ }
+
+ public String getHomeDirPath() {
+ return homeDirPath;
+ }
+
+ public void setHomeDirPath(String path) {
+ if (isNonEmpty(path)) {
+ homeDirPath = path;
+ }
+ }
+
+ public boolean getDisableViewing() {
+ return disableViewing;
+ }
+
+ public void setDisableViewing(boolean disableViewing) {
+ this.disableViewing = disableViewing;
+ }
+
+ private static boolean isNonEmpty(String n) {
+ return n != null && !n.trim().equals("");
+ }
+
+}