Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Internet Janitor
May 17, 2008

"That isn't the appropriate trash receptacle."
Amusingly you can go the other way around very easily. If you're on *nix you might be able to write a little plugin for your window manager to forward the necessary event info to a Java application.

Adbot
ADBOT LOVES YOU

iam
Aug 5, 2011
I'm only a relative noob myself - but isn't this pretty much exactly what enum types are for? That is, they store a set of fixed constants (i.e. tank weight, speed etc) and will allow him to do stuff like:

code:
public class TankGame {

    public static void main(String[] args) {
        Tank tank = TankFactory.createTank(Tank.LIGHTTANK);
        System.out.println("Light tank is rated: " + tank.getTankRating());

        Tank tank2 = Tank.STANDARD;
        System.out.println("Standard tank is rated: " + tank2.getTankRating());
    }

}

class TankFactory {

    public static Tank createTank(Tank type) {

       switch (type) {
            case LIGHTTANK:
                return Tank.LIGHTTANK;
            default:
                return Tank.STANDARD;
       }

    }
}

public enum Tank {
    LIGHTTANK(15.5,75),
    STANDARD(12.5,85);

    private final double speedMph;
    private final double hullStrength; // Percentage based

    Tank(double speedMph, double hullStrength) {
        this.speedMph = speedMph;
        this.hullStrength = hullStrength;
    }

    double speedMph() { return speedMph; }
    double hullStrength() { return hullStrength; }

    double getTankRating() {
        return (speedMph/STANDARD.speedMph())*(hullStrength/STANDARD.hullStrength());
    }

}
Although with that said, as you mention enums aren't as flexible/aren't extensible. So maybe something like:

code:
import tank.Tank.TankType;

public class Main {

    public static void main(String[] args) {
        Tank tank = TankFactory.createTank(TankType.LIGHTTANK);
        System.out.println("Light tank is rated: " + tank.type().getTankRating());
        System.out.println("Light tank has " + tank.getAmmo() + " rounds of ammunition");
    }

}

class TankFactory {

    public static Tank createTank(TankType tankType) {

       switch (tankType) {
            case LIGHTTANK:
                System.out.println("Creating " + tankType.toString() + "...");
                return new Tank(tankType);
            default:
                return new Tank(tankType);
       }

    }
}
code:
public class Tank {

    public enum TankType {

        LIGHTTANK(15.5, 75),
        STANDARD(12.5, 85);

        private final double speedMph;
        private final double hullStrength; // Percentage based

        TankType(double speedMph, double hullStrength) {
            this.speedMph = speedMph;
            this.hullStrength = hullStrength;
        }

        double speedMph() {
            return speedMph;
        }

        double hullStrength() {
            return hullStrength;
        }

        double getTankRating() {
            return (speedMph / STANDARD.speedMph()) * (hullStrength / STANDARD.hullStrength());
        }
    }

    private TankType type;
    private double ammoRounds;

    Tank(TankType typeOfTank) {
        this.type = typeOfTank;

        initialiseAmmunition();

    }

    public TankType type() {
        return this.type;
    }

    public double getAmmo() {
        return ammoRounds;
    }

    private void initialiseAmmunition() {
        switch (type) {
            case LIGHTTANK:
                this.ammoRounds = 200;
            default:
                this.ammoRounds = 500;
       }
    }
}
I dunno, that may not be the best way to go about it - but using string comparison for something like this doesn't sound right to me, nor does a whole bunch of if-elses (though as you note Java 7 now has the switch on string).

iam fucked around with this message at 21:50 on Aug 15, 2011

Hidden Under a Hat
May 21, 2003
I'm trying to write a command to a device and read a response through an ethernet-serial hub, and I get the following error:

java.net.socketexception:
Software caused connection abort: socket write error

I know this is only tangentially related to Java since it seems to be a networking issue, but I was still hoping someone might have experienced this or know anything about it. The odd thing is that this error occurs if I try to communicate with the device once every two minutes, but not if I do it every minute instead. I would appreciate any insight into this problem.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
The fact that it works if you poll the device more often suggests that the connection is being closed due to not having any traffic.

How exactly are you communicating with the device?

Hidden Under a Hat
May 21, 2003

Jabor posted:

The fact that it works if you poll the device more often suggests that the connection is being closed due to not having any traffic.

How exactly are you communicating with the device?

The device is connected to a 8-port serial hub via RS232, and the hub is connected to the computer via ethernet cable. I use Socket(IP Address, port) to connect, then basic InputStream and OutputStream to talk to it. I keep the connection open and communicate with it during each iteration of a loop in a thread.

I'll have to be dig around in the serial hub configuration to see if there is an actual setting that causes port closure due to lack of activity, but regardless if what you mentioned is the problem, would setting setKeepAlive(true) on the socket be sufficient to prevent closure?

Hidden Under a Hat fucked around with this message at 03:20 on Aug 16, 2011

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

Hidden Under a Hat posted:

I'll have to be dig around in the serial hub configuration to see if there is an actual setting that causes port closure due to lack of activity, but regardless if what you mentioned is the problem, would setting setKeepAlive(true) on the socket be sufficient to prevent closure?

"Try it and see" would be my recommendation. It does seem like that is the sort of problem which setKeepAlive(true) would fix...

iam
Aug 5, 2011
Has anyone any experience of using RXTX (the serial comms library) in Java? I'm using it to talk to some RFID readers I've got as part of my MSc project, but sending/writing data is a real pita.

So at the moment I have a thread (x) that's started when a write wants to be carried out. That then starts another thread (y) which does the actual writing. This allows me to sleep thread x to control the timing of writes.

The problem with this method is that I have to pause for 500msec at the start of the thread to ensure that the last thread has finished writing - obviously this doesn't bode well when scaling up to have a dozen or more RFID readers. I've tried unsuccessfully to reduce the wait - any smaller than 500msec and writing becomes unreliable (at 0msec it just doesn't work).

If I don't do this, from what I can tell, the writes interrupt each other - i.e. writes are carried out simultaneously and things break badly. What I want is a way of writing only when I know the previous transmission has successfully been sent.

(I've just had a thought - would using different output streams work/make a difference?)

Anyone with any experience with playing with serial/RXTX got any advice?

Contra Duck
Nov 4, 2004

#1 DAD
Is there any reason why you can't just have each thread grab a lock when it starts writing and release it when it's done?

Kilson
Jan 16, 2003

I EAT LITTLE CHILDREN FOR BREAKFAST !!11!!1!!!!111!
I've used the RXTX library a bunch, but I'd say the same thing as Contra Duck. Just synchronize the method doing the writing, or acquire a lock, or put writes in a shared queue to be written serially, or something.

iam
Aug 5, 2011
Okay cheers - concurrency is not my strong point tbh, I'll do some reading and see what I can come up with!

trex eaterofcadrs
Jun 17, 2005
My lack of understanding is only exceeded by my lack of concern.
Maybe I am misunderstanding something but are you recycling the same output stream for each thread?

Sedro
Dec 31, 2008
I'm having a strange problem with multicast sockets. After starting and stopping senders and receivers a few times, the receiver silently fails (no error, but never receives). The receiver shows up when I run netstat -bano. This happens on two Windows 7 machines.
code:
public class Sender {
	static final String group = "239.255.255.250";
	static final int srcPort = 0;
	static final int dstPort = 3702;
	
	public static void main(String[] args) throws Exception {
		String host = InetAddress.getLocalHost().getHostName();
		String msg = "Hello from " + host;
		
		InetSocketAddress addr = new InetSocketAddress(host, srcPort);
		DatagramSocket socket = null;
		try {
			socket = new DatagramSocket(addr);
			InetAddress grp = InetAddress.getByName(group);
			
			DatagramPacket packet = new DatagramPacket(msg.getBytes(), msg.length(), grp, dstPort);
			while (true) {
				socket.send(packet);
				System.out.println("Sent: " + msg);
				Thread.sleep(5000);
			}
		} finally {
			if (socket != null)
				socket.close();
		}
	}
}
code:
public class Receiver {
	static final String group = "239.255.255.250";
	static final int port = 3702;
	
	public static void main(String[] args) throws Exception {
		InetAddress grp = InetAddress.getByName(group);
		MulticastSocket msocket = null;
		try {
			msocket = new MulticastSocket(port);
			msocket.joinGroup(grp);
	        
			byte[] inbuf = new byte[1024];
			DatagramPacket packet = new DatagramPacket(inbuf, inbuf.length);
			while (true) {
				msocket.receive(packet);
				String data = new String(packet.getData(), 0, packet.getLength());
				System.out.println(String.format("Received from %s - %s", packet.getSocketAddress(), data));
			}
		} finally {
			if (msocket != null) {
				msocket.leaveGroup(grp);
				msocket.close();
			}
		}
	}
}

Sedro
Dec 31, 2008
I figured it out (at least I haven't been able to reproduce the problem again). Apparently the receiver was picking a network interface at random. The fix was to modify the receiver from:
code:
msocket = new MulticastSocket(port);
msocket.joinGroup(grp);
to
code:
msocket = new MulticastSocket(port);
InetAddress localHost = InetAddress.getLocalHost();
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(localHost);
InetSocketAddress groupAddress = new InetSocketAddress(group, 0);
msocket.joinGroup(groupAddress, networkInterface);

ShardPhoenix
Jun 15, 2001

Pickle: Inspected.

Abrahamsen posted:

Problem:
I'm in the beginning phase of creating my first game using Java with JLWGL and Slick. My problem is sort of generic in programming nature.
I am building an entity/component system and I would like to have a list of entity templates with associated components, so that I can have just one method for creating entities with the parameter being the ID of the entity template.
Pseudo code example:

code:
entityManager.createEntity("LIGHT_TANK2");
this should find the template with matching ID "LIGHT_TANK2" and create an entity with the right components added.

The question: How do I make these different entity templates efficiently, and without giving the user the ability to edit them?

Please note: I am a beginner programmer, but catching on fast - some solutions might be too advanced for me.

Edit: Do I use enums? I have no experience with those so I don't know!
This whole concept seems a bit complex/over-abstract for an inexperienced programmer making their first game. Why not sure just normal classes/interfaces?

slyo
Sep 25, 2007

toasty
I am confused about this method:

code:
    public boolean lineIsCommand(String s) {
        if (!s.startsWith("/"));
        return false;
    }
Won't this always return false?

Standish
May 21, 2001

slyo posted:

I am confused about this method:

code:
    public boolean lineIsCommand(String s) {
        if (!s.startsWith("/"));
        return false;
    }
Won't this always return false?
no it might throw a NullPointerException

Sereri
Sep 30, 2008

awwwrigami

^^^^^ true, still not really what it's supposed to do I guess.

slyo posted:

I am confused about this method:

code:
    public boolean lineIsCommand(String s) {
        if (!s.startsWith("/"));
        return false;
    }
Won't this always return false?

Yes it does. The first semicolon is misplaced, an else -> return true; is also missing.

Malloc Voidstar
May 7, 2007

Fuck the cowboys. Unf. Fuck em hard.
it should just be return s.startsWith("/");

or to make it unreadable and also throw a better exception, return Objects.requireNonNull(s, "Line must not be null.").startsWith("/")

Abrahamsen
Jul 1, 2009

ShardPhoenix posted:

This whole concept seems a bit complex/over-abstract for an inexperienced programmer making their first game. Why not sure just normal classes/interfaces?

I have made games before, these have been in AS3 and quite basic as they have mostly been tests to teach me about interacting with a data acquisition device.

I wanted to do something more elaborate and expandable. So I got hooked on the idea of having entity / component relationships in a very modular way.

It IS very complex and over-abstract for an inexperienced programmer like myself, but I hope the challenge will help me learn a lot (already has) and hopefully let me exercise better programming practice.

Progress is slow though, as I'm self-taught and most of my problems are easily solvable by those with a better understanding of java, but hinders me because I have to spend time searching for answers to simple (or not) questions like "what is a hashmap? or when do I use interfaces?".

Ensign Expendable
Nov 11, 2008

Lager beer is proof that god loves us
Pillbug

Aleksei Vasiliev posted:

it should just be return s.startsWith("/");

or to make it unreadable and also throw a better exception, return Objects.requireNonNull(s, "Line must not be null.").startsWith("/")

Wow, I never read about this feature before. Seems really useful. Too bad my workplace is using ancient versions of Java for no good reason. It's not like we have apps that rely on specific hacks to make them work, one of the devs just refuses to upgrade to new versions.

ivantod
Mar 27, 2010

Mahalo, fuckers.

Ensign Expendable posted:

Wow, I never read about this feature before. Seems really useful. Too bad my workplace is using ancient versions of Java for no good reason. It's not like we have apps that rely on specific hacks to make them work, one of the devs just refuses to upgrade to new versions.

Well this one is only in Java 7, so that's going to take a while anyway. Even the stable version of Eclipse doesn't support Java 7 yet (although I think various betas do).

Malloc Voidstar
May 7, 2007

Fuck the cowboys. Unf. Fuck em hard.
You only need 1.5 for that AFAIK, just stick this in a utility class:
code:
public static <T> T requireNonNull(T obj, String message) {
	if (obj == null)
		throw new NullPointerException(message);
	return obj;
}
The Objects class is just some simple short methods that are often useful.

chippy
Aug 16, 2006

OK I DON'T GET IT
Please someone help me with a remedial-level question.

I'm trying to get a String[] from an ArrayList<String> but I'm getting some weird messages:

code:
results =  traceSetList.toArray();
This is causing Eclipse to warn me "Type mismatch - cannot convert from Object() to String()";

code:
results = (String[]) traceSetList.toArray();
Whereas this compiles but causes an exception when I try and use it: "java.lang.ClassCastException: java.lang.Object; cannot be cast to java.lang.String;".

I'm aware I've probably done something very simple wrong but I can't for the life of me work out what it is. Anyone?

edit: Worked it out, thank you, please feel free to ignore or heap scorn as desired.

chippy fucked around with this message at 17:04 on Aug 23, 2011

baquerd
Jul 2, 2007

by FactsAreUseless

chippy posted:

Please someone help me with a remedial-level question.

code:
results = (String[]) traceSetList.toArray();

This works as long as result declared as a String[].

Edit: added a 0 to make this compile
code:
result = traceSetList.toArray(new String[0]);
If you have dimensioned result already and want to populate it:

code:
traceSetList.toArray(result);

baquerd fucked around with this message at 17:08 on Aug 23, 2011

Kilson
Jan 16, 2003

I EAT LITTLE CHILDREN FOR BREAKFAST !!11!!1!!!!111!
Having a really stupid problem that I can't figure out. I'm hoping it's completely dumb and that I'm dumb.

I have a TreeSet, and I insert 10 objects into it. When I then print the contents, there are only 4 items in the set.

This is basically the class I'm using.

code:
public class PuntRule implements Comparable<PuntRule> {

	private List<Integer> trunkGroups;
	private String extensionCode;
	private String prefix;
	private int extensionLength;
	private int outputLength;

// getters + setters are defined

	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final PuntRule other = (PuntRule) obj;
		if (this.trunkGroups != other.trunkGroups && (this.trunkGroups == null || !this.trunkGroups.equals(other.trunkGroups))) {
			return false;
		}
		if ((this.prefix == null) ? (other.prefix != null) : !this.prefix.equals(other.prefix)) {
			return false;
		}
		if (this.extensionLength != other.extensionLength) {
			return false;
		}
		if ((this.extensionCode == null) ? (other.extensionCode != null) : !this.extensionCode.equals(other.extensionCode)) {
			return false;
		}
		if (this.outputLength != other.outputLength) {
			return false;
		}
		return true;
	}

	public int hashCode() {
		int hash = 7;
		hash = 61 * hash + (this.trunkGroups != null ? this.trunkGroups.hashCode() : 0);
		hash = 61 * hash + (this.prefix != null ? this.prefix.hashCode() : 0);
		hash = 61 * hash + this.extensionLength;
		hash = 61 * hash + (this.extensionCode != null ? this.extensionCode.hashCode() : 0);
		hash = 61 * hash + this.outputLength;
		return hash;
	}

	@Override
	public int compareTo(PuntRule other) {
		int thisLen = this.extensionCode.length();
		int otherLen = other.extensionCode.length();
		if (thisLen > otherLen) {
			return -1;
		} else if (thisLen < otherLen) {
			return 1;
		} else {
			return 0;
		}
	}
}
The 10 instances I'm trying to add to the TreeSet look like this:

code:
PuntRule{prefix=3125551003, trunkGroups=[20, 21], extensionLength=4, extensionCode=2006, outputLength=10}

PuntRule{prefix=3124321000, trunkGroups=[], extensionLength=5, extensionCode=22001, outputLength=10}

PuntRule{prefix=, trunkGroups=[60], extensionLength=4, extensionCode=2008, outputLength=4}

PuntRule{prefix=555, trunkGroups=[], extensionLength=11, extensionCode=212, outputLength=10}

PuntRule{prefix=3124325000, trunkGroups=[], extensionLength=11, extensionCode=1, outputLength=10}

PuntRule{prefix=216702, trunkGroups=[], extensionLength=4, extensionCode=2000, outputLength=10}

PuntRule{prefix=3124325000, trunkGroups=[], extensionLength=5, extensionCode=5, outputLength=10}

PuntRule{prefix=312432, trunkGroups=[], extensionLength=4, extensionCode=1, outputLength=10}

PuntRule{prefix=312555, trunkGroups=[], extensionLength=4, extensionCode=2, outputLength=10}

PuntRule{prefix=31243, trunkGroups=[], extensionLength=5, extensionCode=7, outputLength=10}
As you can see, they are all different, so they shouldn't be eliminated by set uniqueness constraints.

baquerd
Jul 2, 2007

by FactsAreUseless

Kilson posted:

Having a really stupid problem that I can't figure out. I'm hoping it's completely dumb and that I'm dumb.

I have a TreeSet, and I insert 10 objects into it. When I then print the contents, there are only 4 items in the set.

Do you have a debugger? Step into your inserts and see what happens.

Lysidas
Jul 26, 2002

John Diefenbaker is a madman who thinks he's John Diefenbaker.
Pillbug
You should read up on the Comparable interface. Your defined natural ordering is not consistent with equals.

Here's a hint: You're getting four objects back. You have four distinct values for extensionCode.length() as referenced in compareTo.

Kilson
Jan 16, 2003

I EAT LITTLE CHILDREN FOR BREAKFAST !!11!!1!!!!111!

Lysidas posted:

You should read up on the Comparable interface. Your defined natural ordering is not consistent with equals.

Here's a hint: You're getting four objects back. You have four distinct values for extensionCode.length() as referenced in compareTo.

*sigh* I read that earlier, but I kind of stopped after the part where it said:

Comparable posted:

It is strongly recommended (though not required) that natural orderings be consistent with equals.

I thought it was just recommended for some sort of ivory-tower reason, not because the difference would have real effects.

Thanks for pointing out my failure to RTFM.

Max Facetime
Apr 18, 2009

Kilson posted:

I thought it was just recommended for some sort of ivory-tower reason, not because the difference would have real effects.

That "recommended, but not required" bit is basically a promise that if something requires Comparable objects then that something is not going to mix in some calls to .equals(), even though it technically could.

Malloc Voidstar
May 7, 2007

Fuck the cowboys. Unf. Fuck em hard.

Kilson posted:

This is basically the class I'm using.
why are you using ternaries inside if statements

Kilson
Jan 16, 2003

I EAT LITTLE CHILDREN FOR BREAKFAST !!11!!1!!!!111!

Aleksei Vasiliev posted:

why are you using ternaries inside if statements

Those were methods generated by the IDE. :effort:

gonadic io
Feb 16, 2011

>>=
I'm not sure if it belongs in coding horrors, but how does
code:
if (string1.compareTo(" ")<=0)
behave?
From context, it seems to be testing if string1 == "", though no idea why that was used instead of null.

baquerd
Jul 2, 2007

by FactsAreUseless

AlsoD posted:

I'm not sure if it belongs in coding horrors, but how does
code:
if (string1.compareTo(" ")<=0)
behave?
From context, it seems to be testing if string1 == "", though no idea why that was used instead of null.

code:
a=null 
b=""
c=" "

a != b && b != c && c != a //true
See the String.compareTo() javadoc for a discussion on lexicographic string ordering and how that statement behaves.

Also, don't use == for comparing Strings ever unless you are testing reference identity.

baquerd fucked around with this message at 16:15 on Aug 25, 2011

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

baquerd posted:

Also, don't use == for comparing Strings ever unless you are testing reference identity.

I've been writing Java almost every day for months and this screws me over at least once a week. I'm stupid.

(Most of my experience is in Python)

gonadic io
Feb 16, 2011

>>=
I'm pretty sure that the compareTo(" ") was done because he'd heard about the "==" thing, but didn't know about equals(String). The earlier method was basically find a string, else return "" and so I think he wanted to check for failure. I don't know why he didn't just have it return null for failure though.

baquerd
Jul 2, 2007

by FactsAreUseless

AlsoD posted:

I'm pretty sure that the compareTo(" ") was done because he'd heard about the "==" thing, but didn't know about equals(String). The earlier method was basically find a string, else return "" and so I think he wanted to check for failure. I don't know why he didn't just have it return null for failure though.

This is what you are describing:

code:
if (string1 instanceof String) {
  //...
} else {
  return "";
}
There are many reasons why you wouldn't want to return null (or throw an exception). Without more code, it's hard to say why they did anything.

Hidden Under a Hat
May 21, 2003
I have a question about using instanceof. I googled a bit and it seems that using instanceof is considered bad form. I can't think of another easy way to do what I'm trying to do though. I basically have a tabbed pane that a user can dynamically add a choice of two different Instrument classes that extend JPanel which each have a different set of fields/selections. They can add as many instrument panels as they want. When the user hits accept, I basically want my program to go through each added panel and call a method called getSettings() from each panel to get the user-entered settings. The problem is the program doesn't know which Instrument class each panel actually is. Here is what I have so far:

code:
public void getInstrumentConfiguration(){
        
        int instrumentCount = instrumentSettingsTabbedPane.getTabCount();
        instrumentSettings = new String[instrumentCount][6];
        Component tabComponent;
        for(int x = 0;x<instrumentCount;x++){
            tabComponent = instrumentSettingsTabbedPane.getComponent(x);
            if(tabComponent instanceof Instrument1){
                Instrument1 instr1 = (Instrument1)tabComponent;
                instrumentSettings[x] = instr1.getSettings();
            }
            if(tabComponent instanceof Instrument2){
                Instrument2 instr2 = (Instrument2)tabComponent;
                instrumentSettings[x] = instr2.getSettings();
            }
        }       
    }
Can someone give me a reason why doing this is a bad idea and if so, what are my alternatives?

Ensign Expendable
Nov 11, 2008

Lager beer is proof that god loves us
Pillbug
I have never heard of instanceof being a bad idea. You could do something like

code:
Instrument tabComponent = (Instrument)instrumentSettingsTabbedPane.getComponent(x);
instrumentSettings[x] = tabComponent.getSettings();
provided Instrument1 and Instrument2 are subclasses of Instrument that defines getSettings(). This will also call the Instrument1 and Instrument2 getSettings() methods automatically, depending on which Instrument the tabComponent is.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
You could organize your code differently to separate semantic content, like the settings available for various components, from the structure of your UI. Failing that, you could at least make an interface with a getSettings() method and test for that rather than N individual subclasses.

Adbot
ADBOT LOVES YOU

HFX
Nov 29, 2004
Using it for the method you describe cries that you should implement an interface that Instrument classes subclass to provide that method. It is considered bad form when you are using it to basically serve as the old dreaded C 200 line if..else if.. statement. Basically, used too liberally it can point to issues with design.

Now that I've said that, it is important to remember that instanceof is the only way to handle certain situations (especially when dealing with code older then Java 5). People who automatically dismiss it as bad, are the same people who think dynamic_cast in C++ is terrible.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply