sI use a simple text-file like this
BMG-P (someLongComplicatedExpression)(.*P)
BMG T (someLongComplicatedExpression)(.*[Tt])
BMG MPA (someLongComplicatedExpression)(.*MPA)
to configure my application (Simple import with bufferedReader.readLine().split("\t")). What is bugging me is the redundance.
I am thinking about a solution like this:
%s=(someLongComplicatedExpression)
BMG-P %s(.*P)
BMG T %s(.*[Tt])
BMG MPA %s(.*MPA)
where I read the value of my variables (like %s), then replace their occurrences in the Strings after the import.
My questions are:
What alternative approaches do you know?
What is an easy way to implement the replacement of my variables in my code?
Can you point me to any frameworks that support property-files like that?
I wrote this simple extension to the Java Properties class:
import java.io.Serializable;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Allows properties to contain expansions of the form ${propertyName}. This
* class makes no attempt to detect circular references, so be careful.
*/
public class ExpandingProperties extends Properties implements PropertySource {
private static final long serialVersionUID = 259782782423517925L;
private final Expander expander = new Expander();
#Override
public String getProperty(String key) {
return expander.expand(super.getProperty(key), this);
}
}
class Expander implements Serializable {
private static final long serialVersionUID = -2229337918353092460L;
private final Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
/**
* Expands variables of the form "${variableName}" within the
* specified string, using the property source to lookup the
* relevant value.
*/
public String expand(final String s, final PropertySource propertySource) {
if (s == null) {
return null;
}
final StringBuffer sb = new StringBuffer();
final Matcher matcher = pattern.matcher(s);
while (matcher.find()) {
final String variableName = matcher.group(1);
final String value = propertySource.getProperty(variableName);
if (value == null) {
throw new RuntimeException("No property found for: " + variableName);
}
matcher.appendReplacement(sb, value.replace("$", "\\$"));
}
matcher.appendTail(sb);
return sb.toString();
}
}
interface PropertySource {
String getProperty(String key);
}
Example usage:
public static void main(String[] args) {
Properties properties = new ExpandingProperties();
properties.put("myVar", "myLongExpression");
properties.put("foo", "${myVar}_1");
properties.put("bar", "${foo}_abc");
System.out.println(properties.getProperty("bar"));
}
Prints:
myLongExpression_1_abc
As ExpandingProperties is an extension of Properties it inherits all the load...() methods for loading values from property files.
An alternative is EProperties which does a similar thing to the above code, but goes even further and allows you to nest property files etc. I found it overkill for what I needed.
Related
I have lots of classes representing user screens in the application that I am testing (page objects). Each of the page objects contains various number of elements with wach of the having an ID. I need to declare those IDs. It looks something like this:
public class MessagesScreen extends BasePage {
private static final String backArrowButtonID = PACKAGE_NAME_ID + "backArrow";
private static final String noMessagesImageID = PACKAGE_NAME_ID + "empty_messages_icon";
private static final String noMessagesTextViewI = PACKAGE_NAME_ID + "label_no_documents";
private static final String errorTextViewID = PACKAGE_NAME_ID + "text_error_response";
private static final String progressBarID = PACKAGE_NAME_ID + "message_list_progress_bar";
private static final String messagesRecyclerViewID = PACKAGE_NAME_ID + "messageList";
private static final String swipeRefreshLayoutID = PACKAGE_NAME_ID + "swipeRefreshLayout";
private static final String instantMessageLayoutID = PACKAGE_NAME_ID + "item_instant_message_layout";
}
Each of the IDs needs to have the an environmental variable added as above.
My question is: is there any nifty way of not having to repeat this PACKAGE_NAME_ID all the time? Is there a way of automatic assigning the constant string to every new class field?
Using an enum here will improve your code in many ways. In addition to solving your current problem, you'd be using the right tool for constants.
enum IdKey {
BACK_ARROW_BUTTON_ID("backArrow"),
NO_MESSAGES_IMAGE_ID("empty_messages_icon"),
NO_MESSAGES_TEXT_VIEW_ID("label_no_documents"),
ERROR_TEXT_VIEW_ID("text_error_response"),
PROGRESS_BAR_ID("message_list_progress_bar"),
MESSAGES_RECYCLER_VIEW_ID("messageList"),
SWIPE_REFRESH_LAYOUT_ID("swipeRefreshLayout"),
INSTANT_MESSAGE_LAYOUT_ID("item_instant_message_layout");
private final String suffix;
private IdKey(String suffix) {
this.suffix = suffix;
}
public String getSuffix() {
return BasePage.PACKAGE_NAME_ID + this.suffix;
}
}
And all you'd have to do wherever you were using your fields is just call:
IdKey.BACK_ARROW_BUTTON_ID.getSuffix();
And the code is both more reliable and cleaner. You can even nest the enum as a private member of MessagesScreen.
I might consider an enum in this case, you can still reference a specific value in the code and override the toString to return your message in the format you want
I have lots of classes representing user screens in the application that I am testing (page objects). Each of the page objects contains various number of elements with wach of the having an ID. I need to declare those IDs. It looks something like this:
public enum MessagesScreen {
BACK_ARROW("backArrow"),
NO_MESSAGES_IMAGE("empty_messages");
private String value;
private Messages(String value) {
this.value = value;
}
#Override public String toString() {
return PACKAGE_NAME_ID + this.value;
}
}
As you cannot enrich the state of final variables after their valuing, I would do things in the other way : providing a getter that adds the desired prefix.
public class MessagesScreen extends BasePage {
private static final String backArrowButtonID = "backArrow";
private static final String noMessagesImageID = "empty_messages_icon";
///...
public String getIdWithPrefix(String constantValue){
return PACKAGE_NAME_ID + constantValue;
}
}
Note that with an enum it would be more robust as Strings accept a very large range of possible values whereas enums don't.
It will require you to change many classes but it is worthful.
The ernest_k answer shows that very well.
I was able to get class level variable's declarations using the following code. But I only need the variable name. This is the output I get for following code - [private boolean flag = true;]
import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import java.io.FileInputStream;
public class CuPrinter{
public static void main(String[] args) throws Exception {
// creates an input stream for the file to be parsed
FileInputStream in = new FileInputStream("C:\\Users\\arosh\\IdeaProjects\\Bot_Twitter\\src\\MyBot.java");
CompilationUnit cu;
try {
// parse the file
cu = JavaParser.parse(in);
} finally {
in.close();
}
cu.accept(new ClassVisitor(), null);
}
private static class ClassVisitor extends VoidVisitorAdapter<Void> {
#Override
public void visit(ClassOrInterfaceDeclaration n, Void arg) {
/* here you can access the attributes of the method.
this method will be called for all methods in this
CompilationUnit, including inner class methods */
System.out.println(n.getFields());
super.visit(n, arg);
}
}
}
You can use the following simple regex:
final String regex = "^((private|public|protected)?\\s+)?.*\\s+(\\w+);$";
Which then can be compiled into a Pattern:
final Pattern pattern = Pattern.compile(regex);
And then finally be used in a for-loop:
for(final String field : n.getFields()){
// create a regex-matcher
final Matcher matcher = pattern.matcher(field);
// if field matches regex
if(matcher.matches()){
// get the last group -> the fieldName
final String name = matcher.group(matcher.groupCount());
System.out.println("FieldName: " + name);
}
}
You can try this. If you have more than one variables in FieldDeclarations, use one more for loop inside.
public void visit(ClassOrInterfaceDeclaration n, Void arg) {
super.visit(n, arg);
for(FieldDeclaration ff:n.getFields())
{
System.out.println(ff.getVariable(0).getName());
}
}
My goal is to use the periodic table of elements (or a list) to get information about a specific element in Java. I want to search it by atomic number and symbol (but that conversion should be simple).
I found that information in this JQuery plugin. But it is stored as a JSON file.
It seems like it would be most efficient to hardcode the information (since it doesn't change too often and due to performance reasons), but how do I convert JSON to a hardcoded enum?
Since:
the information about elements is totally static
each elemental symbol is alphanumeric
the discovery of new elements is both rare and irrelevant (because they are extremely unstable)
An enum seems a good option:
public enum Element {
H(1, "Hydrogen", 1.008, -259.1),
He(2, "Helium", 4.003, -272.2),
Li(3, "Lithium", 6.941, 180.5),
// ... 90+ others
;
private static class Holder {
static Map<Integer, Element> map = new HashMap<Integer, Element>();
}
private final int atomicNumber;
private final String fullName;
private final double atomicMass;
private final double meltingPoint;
private Element(int atomicNumber, String fullName, double atomicMass, double meltingPoint) {
this.atomicNumber = atomicNumber;
this.fullName = fullName;
this.atomicMass = atomicMass;
this.meltingPoint = meltingPoint;
Holder.map.put(atomicNumber, this);
}
public static Element forAtomicNumber(int atomicNumber) {
return Holder.map.get(atomicNumber);
}
public int getAtomicNumber() {
return atomicNumber;
}
public String getFullName() {
return fullName;
}
public double getAtomicMass() {
return atomicMass;
}
public double getMeltingPoint() {
return meltingPoint;
}
}
There's a bit of java kung fu going on here that deserves an explanation. The map is put inside a static inner (holder) class so it gets initialized before the enum instances are initialized, that way they can add themselves to it. If not in the inner static class, it would not be initialize, because the first thing initialized in the enum class must be the instances, but static inner classes are initialized before the class is initialized.
This approach means the the instances don't need to be listed in any particular order (they could be alphabetical listed, or otherwise).
Suppose you have a PeriodicTable.txt file with following format:
ATOMIC_NUMBER SYMBOL OTHER_INFO
Like:
1 H Hydrogen -> Lightest element
2 He Helium -> Second lightest element
3 Li Lithium -> Third lightest element
// and so on...
Then you can have a fairly straightforward implementation of your own PeriodicTable like following:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class PeriodicTable
{
private List<Element> elements;
public PeriodicTable() throws IOException
{
elements = new ArrayList<>();
List<String> list = Files.readAllLines(Paths.get("PeriodicTable.txt"));
list.forEach(this::process);
}
public static void main(String[] args) throws IOException
{
final PeriodicTable periodicTable = new PeriodicTable();
System.out.println(periodicTable.getElementByNumber(1));
System.out.println(periodicTable.getElementBySymbol("Li"));
}
private void process(String line)
{
try (Scanner scanner = new Scanner(line))
{
int atomicNumber = scanner.nextInt();
String symbol = scanner.next();
String info = scanner.nextLine();
elements.add(new Element(atomicNumber, symbol, info));
}
}
public Element getElementByNumber(int atomicNumber)
{
return elements.stream().
filter(e -> e.atomicNumber == atomicNumber).
findFirst().orElse(null);
}
public Element getElementBySymbol(String symbol)
{
return elements.stream().
filter(e -> e.symbol.equals(symbol)).
findFirst().orElse(null);
}
private class Element
{
private String info;
private int atomicNumber;
private String symbol;
public Element(int atomicNumber, String symbol, String info)
{
this.atomicNumber = atomicNumber;
this.symbol = symbol;
this.info = info;
}
public String toString()
{
return "[ " + atomicNumber + " " + symbol + " " + info + " ]";
}
}
}
If you see, I have created an Element class which holds the atomic number, symbol, and info of the element, and I have a list of elements in PeriodicTable class.
I read the PeriodicTable data from the PeriodicTable.txt file and process each line of the text file by parsing it appropriately and creating element for each line and adding it to the elements.
I also add two methods for filtering the element based on atomic number and symbol properties.
The code works in Java 8, so you should have at least that to run it, or one can easily write a code for this which will run on earlier JVMs, though it won't be as compact as this one.
Since there are just limited number of elements in the PeriodicTable, I don't bother to have elements sorted by their atomic number though they will be if your PeriodicTable.txt file has elements with increasing atomic number.
Since we know the exact number of elements in the PeriodicTable and its something that doesn't change frequently, the filtering methods take constant time.
All you have to do now is create a proper PeriodicTable.txt file which can then be used by the program.
Note: The PeriodicTable class can be written in better ways as well. This is just an example. I can have it as Singleton. I can even have enum of Element with hardcoded values, but I think loading data from file will keep the code cleaner.
One can even augment the PeriodicTable class with additional properties to each Element, by changing the process() method accordingly, and changing the format of the text file based on the assumptions, and augmenting the Element class, so that it can hold even more information.
Just for fun, following is a Singleton based solution:
// PeriodicTable.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class PeriodicTable
{
private static PeriodicTable periodicTable = new PeriodicTable();
private List<Element> elements;
private PeriodicTable()
{
try
{
elements = new ArrayList<>();
List<String> list = Files.readAllLines(Paths.get("PeriodicTable.txt"));
list.forEach(this::process);
}
catch (IOException e)
{
e.printStackTrace();
}
}
public static Element getElementByNumber(int atomicNumber)
{
return periodicTable.elements.stream().
filter(e -> e.atomicNumber == atomicNumber).
findFirst().orElse(null);
}
public static Element getElementBySymbol(String symbol)
{
return periodicTable.elements.stream().
filter(e -> e.symbol.equals(symbol)).
findFirst().orElse(null);
}
private void process(String line)
{
try (Scanner scanner = new Scanner(line))
{
int atomicNumber = scanner.nextInt();
String symbol = scanner.next();
String info = scanner.nextLine();
elements.add(new Element(atomicNumber, symbol, info));
}
}
private class Element
{
private String info;
private int atomicNumber;
private String symbol;
public Element(int atomicNumber, String symbol, String info)
{
this.atomicNumber = atomicNumber;
this.symbol = symbol;
this.info = info;
}
public String toString()
{
return "[ " + atomicNumber + " " + symbol + " " + info + " ]";
}
}
}
// Demo.java
public class Demo
{
public static void main(String[] args)
{
System.out.println(PeriodicTable.getElementByNumber(1));
System.out.println(PeriodicTable.getElementBySymbol("Li"));
}
}
Now you can use your PeriodicTable safely and directly as shown in the Demo method.
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
public class LoadSettings
{
public static void LoadMySettings (Context ctx)
{
SharedPreferences sharedPreferences = ctx.getSharedPreferences("MY_SHARED_PREF", 0);
String strSavedMem1 = sharedPreferences.getString("gSendTo", "");
String strSavedMem2 = sharedPreferences.getString("gInsertInto", "");
String cCalId = sharedPreferences.getString("gCalID", "");
setInsertIntoStr(strSavedMem2);
setSendToStr(strSavedMem1);
}
private static String cSendToStr;
private static String cInsertIntoStr;
private int cCalId;
private Uri cCalendars;
public String getSendToStr()
{
return this.cSendToStr;
}
public static void setSendToStr(String pSendToStr)
{
cSendToStr = pSendToStr;
}
public String getInsertIntoStr()
{
return this.cInsertIntoStr;
}
public static void setInsertIntoStr(String pInsertIntoStr)
{
cInsertIntoStr = pInsertIntoStr;
}
}
from the calling class i have tryed lots the current is.
LoadSettings.LoadMySettings(this);
but when i try to get some data for example.
textSavedMem1.setText(LoadSettings.getSendToStr());
i get a Null error.
LoadMySettings is not a class but a method (so it should start with a lower case, if you follow Oracle/Sun's naming conventions for the Java language).
You access it indeed by calling LoadSettings.loadMySettings(someContext), where someContext is the context to pass around. In your example, we don't know what this refers to, so maybe your error lies there.
Then when you do this: textSavedMem1.setText(LoadSettings.getSendToStr());
You call a non-static method, so that should be either using an instance of LoadSettings or, more likely considering your code, you could change getSendToStr to be:
public static String getSendToStr()
{
return cSendToStr;
}
Though that seems to be rather bad design.
Maybe if you tell us more about what you try to do, we can help more, as as such our answers will just take you one step further.
EDIT: Ok, I just figured out what you are trying to do...
You need to go back and learn basic Java concepts and read on access modifiers, and constructors first, and OO semantics in Java in general.
Change your class to this:
public class LoadSettings
{
public LoadSettings(Context ctx)
{
SharedPreferences sharedPreferences =
ctx.getSharedPreferences("MY_SHARED_PREF", 0);
String strSavedMem1 = sharedPreferences.getString("gSendTo", "");
String strSavedMem2 = sharedPreferences.getString("gInsertInto", "");
String cCalId = sharedPreferences.getString("gCalID", "");
setInsertIntoStr(strSavedMem2);
setSendToStr(strSavedMem1);
}
private String cSendToStr;
private String cInsertIntoStr;
private int cCalId;
private Uri cCalendars;
public String getSendToStr()
{
return cSendToStr;
}
public void setSendToStr(String pSendToStr)
{
cSendToStr = pSendToStr;
}
public String getInsertIntoStr()
{
return cInsertIntoStr;
}
public void setInsertIntoStr(String pInsertIntoStr)
{
cInsertIntoStr = pInsertIntoStr;
}
}
And create a new instance of LoadSettings with:
LoadSettings mySettings = new LoadSettings(someContext);
You can then correctly invoke:
textSavedMem1.setText(mySettings.getSendToStr());
Haylem has the right of it, but I just wanted to add a comment:
There are basically two design patterns in Java for what you're trying to do. One is the static class where all the methods and variables are static and you access them as e.g.
LoadSettings.loadMySettings(this);
string = LoadSettings.getSendToStr()
// etc.
The other pattern is the "singleton" class where you create exactly one instance of the class and you access the instance:
LoadSettings ls = new LoadSettings(this);
ls.loadMySettings();
string = ls.getSendToStr();
Either way is good, but what you're doing is a mish-mash of both methods and it won't work.
this is the issue at hand, when trying to serialize the class below with the code below i'm getting is the below xml file without all the strings in the class.
The Class (some static values have changed but basically it), I left out all the generated get\set but they are all there with public access modifiers.
public class NotificationConfiguration implements Serializable
{
public static final String PORT_KEY = "mail.smtp.port";
public static final String DEFAULT_PORT_VALUE = "587";
public static final String TTL_KEY = "mail.smtp.starttls.enable";
public static final String DEFAULT_TTL_VALUE = "true";
public static final String AUTH_KEY = "mail.smtp.auth";
public static final String DEFAULT_AUTH_VALUE = "true";
public static final String MAIL_SERVER_KEY = "mail.smtp.host";
public static final String DEFAULT_MAIL_CLIENT_HOST = "smtp.gmail.com";
public static final String DEFAULT_MAIL_CLIENT_USERNAME = "*********";
public static final String DEFAULT_MAIL_CLIENT_PASSWORD = "*********";
public static final String DEFAULT_MAIL_CLIENT_ADDRESS = "*********";
public static final String DEFAULT_ADMIN_EMAIL = "*********";
public static final long DEFAULT_MAIL_INTERVAL = 24*60*60*1000; //One time a day default
public static final String SAVED_FOLDER_NAME = "C:\\Library";
public static final String SAVED_FILE_NAME = "C:\\Library\\NotificationCfg.xml";
private String portValue = DEFAULT_PORT_VALUE;
private String ttlValue = DEFAULT_TTL_VALUE;
private String authValue = DEFAULT_AUTH_VALUE;
private String mailClientHost = DEFAULT_MAIL_CLIENT_HOST;
private String mailClientUserName = DEFAULT_MAIL_CLIENT_USERNAME;
private String mailClientPassword = DEFAULT_MAIL_CLIENT_PASSWORD;
private String mailClientAddress = DEFAULT_MAIL_CLIENT_ADDRESS;
private String adminEMail = DEFAULT_ADMIN_EMAIL;
private boolean overdueSubsNotificationEnabled = false;
private boolean adminReportNotificationEnabled = false;
private long mailInterval =
}
The code used to serialize, which also creates the folder if missing.
public void storeChanges()
{
try
{
try
{
File f = new File(NotificationConfiguration.SAVED_FOLDER_NAME);
f.mkdir();
}
catch (Exception e){}
XMLEncoder encoder = new XMLEncoder( new BufferedOutputStream(new FileOutputStream(NotificationConfiguration.SAVED_FILE_NAME)));
encoder.writeObject(notificationConfig);
encoder.close();
System.out.println(LOG_CONFIGURATION_STORED);
}
catch (Exception ex)
{
System.out.println(LOG_CONFIGURATION_NOT_STORED + ex.getMessage());
}
}
The XML file received, with no exceptions thrown while serializing.
It basically just has the long value.
XMLEncoder encodes information about how to restore your object. If field values haven't changed from their defaults, XMLEncoder doesn't store anything.
This can cause confusion.
Hence, my rules of thumb when using XMLEncoder are:
1. don't initialize fields. don't do private String foo = DEFAULT_FOO;
2. don't do anything in the default constructor.
3. have some other method, or factory that will give you a "default" setup if needed.
I highly recommend to read again the XMLEncoder Javadoc
I will point out the main differences with the binary serialization we all know.
to restore the instance it need the class definition available to the JVM
It serializes only the data. And only the modified from default data.
As result of the 2 points above - is that there is no reason to serialize Static final values - they are part of the class definition.
The binary serialization on the other hand does serialize the class definition and can load from byte stream a class that was not available to the JVM before.
That is why you got results that you see. It Ok this is behavior by design and you use it right. It seems just not to be what you need.
By the way see what Xstream has to offer.
What is SAVED_FOLDER_NAME ? Is that like a factory object and did you by any chance call setMailInterval on that object?
Could that be that only mailInterval has a getter?
Just looked again the question apparently there is getter for all fields so ...