developer.com - Reference
Click here to support our advertisers
SHOPPING
JOB BANK
CLASSIFIEDS
DIRECTORIES
REFERENCE
Online Library
LEARNING CENTER
JOURNAL
NEWS CENTRAL
DOWNLOADS
COMMUNITY
CALENDAR
ABOUT US
Journal:
Get the weekly email highlights from the most popular journal for developers!
Current issue
developer.com
developerdirect.com
htmlgoodies.com
javagoodies.com
jars.com
intranetjournal.com
javascripts.com
All Categories :
Java
Chapter 6
Interfaces
CONTENTS
The Purpose of Java Interfaces
The Benefits of Interfaces
Declaring Interfaces
Implementing Interfaces
The CDrawApp Interface Example
The CGTextEdit Interface
Updating the CGText Class
The CGTextPoint Class
The CGTextBox Class
Updating the CDraw Class
Running the Example
Example Summary
Using Interfaces as Abstract Types
Interface Constants
Extending Interfaces
Combining Interfaces
Summary
In this chapter you'll learn how to use Java interfaces to provide
a common set of methods by which a group of classes can be accessed
and to implement features of multiple inheritance. You'll cover
the use of interface constants and learn how to declare objects
using interface types. You will also learn how to extend and combine
interfaces. When you finish this chapter, you'll be able to use
interfaces with your Java classes.
The Purpose of Java Interfaces
The Java interface construct is borrowed from the Objective-C
protocol. It is used to identify a common set of methods for the
group of classes that implement the interface. It is also used
to share constants between classes. Interfaces are used to provide
the benefits of multiple inheritance without its implementation
difficulties. They allow several classes to share a standard set
of methods and constants without requiring these methods and constants
to be implemented by a common superclass.
Interfaces provide a standard framework for accessing classes.
They are analogous to the interfaces that we encounter in everyday
life. Any large class of real-world objects that you regularly
manipulate usually has a standard interface. Radios and televisions
provide a common set of controls for tuning channels and adjusting
audio volume. Cars come with a standard interface for steering,
throttling, and braking. Automated bank tellers provide the same
general interface for performing bank transactions.
To realize the potential use of Java interfaces, consider the
diversity of objects that are manipulated by GUI-building programs.
Such programs provide the capability to generate graphical user
interfaces by clicking on interface controls and dragging them
to appropriate places on the windows and dialog boxes being developed.
The objects implementing these controls may support many different
sets of methods. For example, one subset of the controls may be
required to support cut, copy, and paste operations. These methods
might be grouped into an EditObject interface. Another
subset of the interface controls may be required to support click
and double-click operations. These objects might implement a Clickable
interface. Another subset may support drag-and-drop operations
and implement the Draggable interface. Other groups of
objects may implement multiple interfaces. For example, there
might be objects that are both Clickable and Draggable.
The
Benefits of Interfaces
Interfaces provide many advantages to the Java programmer. One
is that they allow standard sets of methods to be used across
the class hierarchy. For example, you can define the Editable
interface to support cut, copy, and paste operations. The Editable
interface can then be implemented by relevant classes and establish
a uniform approach to implementing these common operations.
Interface types allow objects to be referenced by the methods
they support without considering their location in the class hierarchy.
They make maximal use of dynamic binding, allowing objects to
be accessed independently of their implementation details. For
example, parameters can be defined as interface types and used
by methods. These methods can invoke the interface methods of
their arguments without having to determine the classes to which
the arguments belong.
Interfaces also support selective multiple inheritance.
They allow various subsets of the features supported by different
classes to be shared without mandating that all features of these
classes be uniformly imposed as the result of inheritance.
Finally, because interfaces are declared independently of classes,
they are unaffected by changes to specific classes or to the class
hierarchy as a whole.
Declaring
Interfaces
Interfaces, like classes, are not objects, but type definitions
that can be used to declare an object. Interfaces are declared
as follows:
InterfaceModifiers interface
InterfaceName ExtendsClause InterfaceBody
The allowed interface modifiers are abstract and public.
By default, all interfaces are abstract. The public
access specifier allows interfaces to be accessed outside of the
package in which they are declared, in the same manner as with
classes. Only one class or interface may be declared public
in a given compilation unit. The compilation unit must have the
same name as its public interface or class. The extends
clause is similar to the class extends clause and is
covered later in this chapter.
The body of an interface begins with an opening brace ({),
consists of zero or more variable or method declarations, and
ends with a closing brace (}).
All variables declared in an interface body are assumed to be
both static and final, must have a constant
initializer, and are implemented as constant class variables.
All methods declared in an interface body are assumed to be abstract
and do not have method bodies. Only access methods can be declared
within an interface; constructors are not allowed. The access
specifier of a method is that of the interface in which it is
declared.
An example of a simple interface declaration is as follows:
public interface Clickable {
void click();
void doubleClick();
}
This Clickable interface is declared as public
so that it can be accessed outside its package. It contains two
method declarations, click() and doubleClick().
These methods must be supported by all classes that implement
the Clickable interface.
Implementing
Interfaces
The interfaces implemented by a class are identified in the implements
clause of the class declaration. For example, the following class
implements the Scrollable and Clickable interfaces:
class ExampleClass implements Scrollable, Clickable {
}
A non-abstract class must implement all interface methods
that are not implemented by its superclasses. abstract
classes are not required to implement interface methods. They
can defer interface implementation to their non-abstract
subclasses.
The
CDrawApp
Interface Example
To provide a concrete example of the use of interfaces, we'll
extend the CDrawApp program, introduced in Chapter 5,
"Classes and Objects," to include support for editable
objects. Editable objects are objects that display text
on the grid and can be edited using the CGTextEdit interface.
The CGText class will be modified to support this interface.
The CGPoint and CGBox classes will be extended
by the subclasses, CGTextPoint and CGTextBox,
both of which provide the capability to display text on the grid.
The CGText, CGTextPoint, and CGTextBox
classes will implement the CGTextEdit interface. Figure 6.1
shows the extensions to the Chapter 5 class
hierarchy that were made to support this example.
Figure 6.1 : Extensions to the CDrawApp class hierarchy.
Before going on to edit and compile the source code files for
this example, be sure to create a ch06 subdirectory under
c:\java\jdg. This subdirectory should be used to store
all the source Java files that you develop in this chapter.
The CGTextEdit
Interface
The CGTextEdit interface is quite simple. Its source
code is shown in Listing 6.1.
Listing 6.1. The CGTextEdit
interface source code.
package jdg.ch06;
public interface CGTextEdit {
public void replaceText(String s);
public void upperCase();
public void lowerCase();
}
The CGTextEdit interface source code declares three methods:
replaceText(), upperCase(), and lowerCase().
These methods must be provided by all classes that implement the
CGTextEdit interface. The replaceString() method
is used to replace the text associated with an object with the
text contained in the String's parameter. The upperCase()
and lowerCase() methods are used to convert the text
associated with an object to upper- and lowercase, respectively.
CGTextEdit and all its interfaces are declared as public,
allowing them to be accessed outside of the jdg.ch06
package. The public modifiers used with the method declarations
are redundant. Any method declared in a public interface
is public, by default.
After you have entered the CGTextEdit interface into
the file, CGTextEdit.java, use javac to compile
CGTextEdit.java. Do this from within the ch06
directory.
Updating the CGText
Class
The CGText class, developed in Chapter 5,
will be updated to implement the CGTextEdit interface.
The easiest way to do this is to copy CGText.java from
the c:\java\jdg\ch05 directory to the c:\java\jdg\ch06
directory and then edit it. Its source code is shown in Listing
6.2.
Listing 6.2. The CGText
class source code.
package jdg.ch06;
import jdg.ch05.CGObject;
import jdg.ch05.Point;
import jdg.ch05.PrintCGrid;
import java.lang.System;
// CGText.java
public class CGText extends CGObject implements CGTextEdit {
// Variable declarations
String text;
// Method declarations
public CGText(Point p,String s) {
location = p;
drawCharacter = ' ';
text = s;
}
public void display(PrintCGrid grid) {
Point p = new Point(location);
for(int i=0;i<text.length();++i){
grid.putCharAt(text.charAt(i),p);
p = p.add(1,0);
}
}
public void describe() {
System.out.println("CGText "+location.toString()+"
"+text);
}
public void replaceText(String s) {
text=s;
}
public void upperCase() {
text = text.toUpperCase();
}
public void lowerCase() {
text = text.toLowerCase();
}
}
All you need to do is to change the package statement,
add the import statements, edit the class declaration,
and add the last three methods that implement the CGTextEdit
interface.
Because this class is contained in the jdg.ch06 package,
you need to import the CGObject, Point, and
PrintCGrid classes from the jdg.ch05 package.
The class declaration is changed to add the implements
clause with the CGTextEdit interface.
The three new methods are all very simple. The replaceText()
method assigns text to the new value passed by the s
parameter. The upperCase() and lowerCase() methods
use the toUpperCase() and toLowerCase() methods
of the String class to perform their conversions.
You should compile the new CGText.java before moving
on to the next class.
The CGTextPoint
Class
The CGTextPoint class extends the CGPoint class
to add the capability to display text along with the character
point. (See Listing 6.3.)
Listing 6.3. The CGTextPoint
class source code.
package jdg.ch06;
import jdg.ch05.Point;
import jdg.ch05.CGPoint;
import jdg.ch05.PrintCGrid;
import java.lang.System;
// CGTextPoint.java
public class CGTextPoint extends CGPoint implements CGTextEdit
{
// Variable declarations
String text;
// Method declarations
public CGTextPoint(Point p,char ch,String s) {
super(p,ch);
text = s;
}
public CGTextPoint(Point p,String s) {
super(p);
text = s;
}
public CGTextPoint(Point p,char ch) {
super(p,ch);
text = "";
}
public CGTextPoint(Point p) {
super(p);
text = "";
}
public void display(PrintCGrid grid) {
super.display(grid);
Point p = location.add(1,0);
for(int i=0;i<text.length();++i){
grid.putCharAt(text.charAt(i),p);
p = p.add(1,0);
}
}
public void describe() {
System.out.print("CGTextPoint "+String.valueOf(drawCharacter)+"
");
System.out.println(location.toString()+" "+text);
}
public void replaceText(String s) {
text=s;
}
public void upperCase() {
text = text.toUpperCase();
}
public void lowerCase() {
text = text.toLowerCase();
}
}
CGTextPoint declares the variable text. This
variable is used to store the text associated with the point.
It provides four constructors, each of which uses the super()
constructor call statement to invoke the constructors of the CGPoint
class. The four constructors allow CGTextPoint to be
constructed using different combinations of parameters.
The display() method invokes the display() method
of its superclass to display the point at its location on the
grid. It then displays the value of the text variable to the immediate
right of this point. The describe() method displays a
description of the text point on the console window. The replaceText(),
upperCase(), and lowerCase() methods are the
same as those of the new CGText class.
The CGTextBox
Class
The CGTextBox class extends the CGBox class
to add the capability to display text within a box. (See Listing
6.4.) The size of the box is automatically fitted to the size
of the text to be displayed.
Listing 6.4. The CGTextBox
class source code.
package jdg.ch06;
import jdg.ch05.Point;
import jdg.ch05.CGBox;
import jdg.ch05.PrintCGrid;
import java.lang.System;
// CGTextBox.java
public class CGTextBox extends CGBox implements CGTextEdit {
// Variable declarations
String text;
// Method declarations
public CGTextBox(Point ulCorner, char ch, String s) {
super(ulCorner,ulCorner.add(s.length()+1,2),ch);
text = s;
}
public CGTextBox(Point ulCorner, String s) {
super(ulCorner,ulCorner.add(s.length()+1,2));
text = s;
}
public void display(PrintCGrid grid) {
super.display(grid);
Point p = location.add(1,1);
for(int i=0;i<text.length();++i){
grid.putCharAt(text.charAt(i),p);
p = p.add(1,0);
}
}
public void describe() {
System.out.print("CGTextBox "+String.valueOf(drawCharacter)+"
");
System.out.println(location.toString()+" "+lr.toString()+"
"+text);
}
public void replaceText(String s) {
text=s;
lr=location.add(text.length()+1,2);
}
public void upperCase() {
text = text.toUpperCase();
}
public void lowerCase() {
text = text.toLowerCase();
}
}
The CGTextBox class source code defines the text variable
in the same manner as the CGTextPoint class and provides
two constructors for initializing objects of its class. Both constructors
use calls to the CGBox class to support the initialization.
The parameters to these calls calculate the lower-right corner
of the box using the upper-left corner as a reference point and
adding horizontal and vertical offsets that size the box based
on the length of the text it contains.
The display() method displays a box using the display()
method of its parent. It then displays text within the
box. The describe() method prints a box's parameters
on the console window.
The upperCase() and lowerCase() methods are
the same as those of the CGTextPoint class, but the replaceText()
method is different. It updates the lr variable to correctly
resize the box based on changes to the length of the text
variable.
Updating the CDraw
Class
The CDraw class is updated to support the Edit Text command.
This requires changes to all its access methods except the addText()
method. The source code of the CDrawApp and CDraw
classes is shown in Listing 6.5.
Listing 6.5. The CDrawApp
and CDraw
classes.
package jdg.ch06;
import jdg.ch05.Point;
import jdg.ch05.CGrid;
import jdg.ch05.PrintCGrid;
import jdg.ch05.BorderedPrintCGrid;
import jdg.ch05.CGObject;
import jdg.ch05.CGPoint;
import jdg.ch05.CGBox;
import jdg.ch05.KeyboardInput;
import java.lang.System;
import java.lang.ClassCastException;
import java.io.IOException;
class CDrawApp {
public static void main(String args[]) throws IOException
{
CDraw program = new CDraw();
program.run();
}
}
class CDraw {
// Variable declarations
static KeyboardInput kbd = new KeyboardInput(System.in);
BorderedPrintCGrid grid;
// Method declarations
CDraw() {
grid = new BorderedPrintCGrid();
}
void run() throws IOException {
boolean finished = false;
do {
char command = getCommand();
switch(command){
case 'P':
addPoint();
System.out.println();
break;
case 'B':
addBox();
System.out.println();
break;
case 'T':
addText();
System.out.println();
break;
case 'U':
grid.deleteLastObject();
System.out.println();
break;
case 'C':
grid.clearGrid();
System.out.println();
break;
case 'S':
grid.show();
break;
case 'E':
editText();
break;
case 'X':
finished = true;
default:
System.out.println();
}
} while (!finished);
}
char getCommand() throws IOException {
System.out.print("CDrawApp P - Add a Point
U - Undo Last Add");
System.out.println(" E - Edit Text");
System.out.print("Main Menu B - Add a Box
C - Clear Grid");
System.out.println(" X - Exit CDrawApp");
System.out.print(" T - Add Text
S - Show Grid");
System.out.print(" Enter command: ");
System.out.flush();
return Character.toUpperCase(kbd.getChar());
}
void addPoint() throws IOException {
System.out.println("Add Point Menu");
System.out.println(" Location:");
Point p = kbd.getPoint();
System.out.print(" Character: ");
System.out.flush();
char ch = kbd.getChar();
if(ch==' ') ch = '+';
System.out.print(" Add text (y/n): ");
System.out.flush();
if('Y'==Character.toUpperCase(kbd.getChar())) {
System.out.print(" Text: ");
System.out.flush();
String s = kbd.getText();
CGTextPoint cgtp = new CGTextPoint(p,ch,s);
cgtp.addToGrid(grid);
}else{
CGPoint cgp = new CGPoint(p,ch);
cgp.addToGrid(grid);
}
}
void addBox() throws IOException {
System.out.println("Add Box Menu");
System.out.println(" Upper Left Corner:");
Point ul = kbd.getPoint();
System.out.print(" Add text (y/n): ");
System.out.flush();
if('Y'==Character.toUpperCase(kbd.getChar())) {
System.out.print(" Text: ");
System.out.flush();
String s = kbd.getText();
System.out.print(" Character: ");
System.out.flush();
char ch = kbd.getChar();
if(ch==' ') ch = '#';
CGTextBox cgtb = new CGTextBox(ul,ch,s);
cgtb.addToGrid(grid);
}else{
System.out.println(" Lower Right Corner:");
Point lr = kbd.getPoint();
System.out.print(" Character: ");
System.out.flush();
char ch = kbd.getChar();
if(ch==' ') ch = '#';
CGBox box = new CGBox(ul,lr,ch);
box.addToGrid(grid);
}
}
void addText() throws IOException {
System.out.println("Add Text Menu");
System.out.println(" Location:");
Point p = kbd.getPoint();
System.out.print(" Text: ");
System.out.flush();
String text = kbd.getText();
CGText cgt = new CGText(p,text);
cgt.addToGrid(grid);
}
void editText() throws IOException {
System.out.println("Current Objects:");
int numObjects = grid.getNumObjects();
for(int i=0;i<numObjects;++i){
System.out.print(" "+String.valueOf(i)+"
");
grid.getObject(i).describe();
}
if(numObjects > 0){
System.out.print("Select an object to edit:
");
System.out.flush();
int objIndex = kbd.getInt();
CGObject obj = grid.getObject(objIndex);
try {
editText((CGTextEdit) obj);
}catch (ClassCastException ex){
System.out.println("Object is not
editable.");
}
}else System.out.println("(none)");
System.out.println();
}
void editText(CGTextEdit obj) throws IOException {
System.out.println("Text Edit Menu");
System.out.println(" R - Replace Text");
System.out.println(" L - Lower Case");
System.out.println(" U - Upper Case");
System.out.print("Enter command: ");
System.out.flush();
char ch = kbd.getChar();
ch = Character.toUpperCase(ch);
switch(ch) {
case 'R':
System.out.print("Enter new text:
");
System.out.flush();
String s = kbd.getText();
obj.replaceText(s);
break;
case 'L':
obj.lowerCase();
break;
case 'U':
obj.upperCase();
break;
}
}
}
The run(), getCommand(), addPoint(),
and addBox() methods are updated to support the Edit
Text command. The two overloaded editText() methods are
added to process this command.
The switch statement of the run() method adds
the 'E' case to its list of command options, calling
the editText() method to process the Edit Text command.
The getCommand() method adds the Edit Text command to
its menu display.
The addPoint() and addBox() methods query the
user to determine whether text should be added to the point or
box. If the user declines to add text, a CGPoint or CGBox
object is created and added to the grid. If the user indicates
that he or she wants to add text to the point or box, the user
is prompted to enter the text. In this case, CGTextPoint
and CGTextBox objects are created and added to the grid.
The two editText() methods share the same name but provide
completely different processing. The first editText()
method is invoked when the user enters the Edit Text command.
It displays a list of the objects that are currently added to
the grid. It does this by using the getNumObjects() method
of the PrintCGrid class to find out how many objects
there are and then retrieving those objects using the getObject()
method of the PrintCGrid class. The following line of
code concatenates two method invocations:
grid.getObject(i).describe();
It retrieves an object of class CGObject by invoking
the getObject() method of the PrintCGrid class.
It then invokes the object's describe() method so that
it will display its description on the console window. If there
are no objects currently added to the grid, the editText()
method indicates this fact by displaying (none) to the
console window. Otherwise, the user is prompted to enter the number
of the object to edit. This number is the number listed in the
current object display. The number entered by the user is used
to retrieve the object to be edited using the getObject()
method. After the object is retrieved, the editText()
method tries to edit the text associated with the object by invoking
the second editText() method. If the object does not
implement the CGTextEdit interface, a ClassCastException
is thrown during the invocation of the second editText()
method. The first editText() method catches this exception
and reports the selected object as not being editable.
The second editText() method displays a Text Edit Menu
prompt to the user and invokes the replaceText(), lowerCase(),
and upperCase() methods to process the commands entered
by the user.
Running the Example
The CDrawApp program is compiled and executed in the
same manner as its Chapter 5 predecessor.
You should notice the additional Edit Text command provided in
the CDrawApp main menu:
C:\java\jdg\ch06>java jdg.ch06.CDrawApp
CDrawApp P - Add
a Point U - Undo Last Add E
- Edit Text
Main Menu B - Add a Box C
- Clear Grid X - Exit CDrawApp
T
- Add Text S - Show Grid Enter
command:
We'll add a few objects to the grid, display them, and then edit
their text. After you learn how to use the new program, we'll
discuss its features as they relate to interfaces.
Enter P to add a point to the grid. Set its x-coordinate
to 60, its y-coordinate to 10, and its draw
character to @:
Add Point Menu
Location:
x-coordinate: 60
y-coordinate: 10
Character: @
Add text (y/n):
You are asked whether you want to add text to the point. Press
Y to add text. You are then prompted to add your text.
Enter at sign as your text, as shown in the following
display output:
Add Point Menu
Location:
x-coordinate: 60
y-coordinate: 10
Character: @
Add text (y/n): y
Text: at sign
The CDrawApp main menu is then redisplayed. Enter B
to add a box. Set the box's upper-left corner as follows:
Add Box Menu
Upper Left Corner:
x-coordinate: 4
y-coordinate: 4
Add text (y/n):
Enter Y to add text to the box. You are prompted to enter
your text. Enter the text Java's interfaces support multiple
inheritance.. Then set the box's draw character to +.
Your display output should look like the following:
Add Box Menu
Upper Left Corner:
x-coordinate: 4
y-coordinate: 4
Add text (y/n): y
Text: Java's interfaces support multiple inheritance.
Character: +
Enter B to enter another box. This box will not contain
any text. Enter the coordinates for the box's corners, as follows:
Add Box Menu
Upper Left Corner:
x-coordinate: 65
y-coordinate: 15
Add text (y/n): n
Lower Right Corner:
x-coordinate: 72
y-coordinate: 18
Then set its draw character to a hyphen:
Character: -
You should have noticed that when a box contains text, its lower-right
corner is not specified. That's because the program computes it
based on the length of the text to be displayed with the box.
You're almost done adding objects to the grid. Enter T
to add text to the grid. Set the text's location and value as
follows:
Add Text Menu
Location:
x-coordinate: 1
y-coordinate: 18
Text: UPPER CASE Or lower case
You now have enough objects to work with. Enter S to
display the grid. It should look like this:
***************************************************************************
* *
* *
* *
* *
* +++++++++++++++++++++++++++++++++++++++++++++++++ *
* +Java's interfaces support multiple inheritance.+ *
* +++++++++++++++++++++++++++++++++++++++++++++++++ *
* *
* *
* *
* @ at sign *
* *
* *
* *
* *
* -------- *
* - - *
* - - *
* UPPER CASE Or lower case -------- *
* *
***************************************************************************
Let's start editing these objects. Enter E to select
an object to edit:
CDrawApp P - Add a Point U
- Undo Last Add E - Edit Text
Main Menu B - Add a
Box C - Clear Grid X
- Exit CDrawApp
T
- Add Text S - Show Grid Enter
command: e
Current Objects:
0 CGTextPoint @ (60,10) at sign
1 CGTextBox + (4,4) (52,6) Java's interfaces support multiple
inheritance.
2 CGBox - (65,15) (72,18)
3 CGText (1,18) UPPER CASE Or lower case
Select an object to edit:
A list of the grid's current objects is displayed. Enter 2
to select the object of class CGBox. Because this object
does not implement the CGTextEdit interface, it is identified
as not being editable, as shown in the following console output:
Object is not editable.
See if you can find where this processing was performed within
the CDraw class. Enter E again to edit another
object. The list of current objects is again displayed. Enter
1 to select the object of class CGTextBox. The
Text Edit Menu prompt is displayed as follows:
Text Edit Menu
R - Replace Text
L - Lower Case
U - Upper Case
Enter command:
This menu allows you to use the methods of the CGTextEdit
interface to edit the objects that implement the interface. Enter
R to replace the text associated with the CGTextBox
object. You are then prompted to enter the new text for this object.
Enter interfaces to complete the editing. Your display
should contain the following output:
Enter command: r
Enter new text: interfaces
Enter S to see how the grid was updated. Notice how the
size of the CGTextBox was changed to fit the size of
the text it contains:
++++++++++++
+interfaces+
++++++++++++
Enter E and then 0 to edit the object of class
CGTextPoint. Then type U to change it to uppercase.
Use the show command to verify that the text has been
changed to uppercase.
Enter E, 3, and L in succession to
change the case of the text contained in the CGText object.
Use the Show Grid command to redisplay the grid:
***************************************************************************
* *
* *
* *
* *
* ++++++++++++ *
* +interfaces+ *
* ++++++++++++ *
* *
* *
* *
* @ AT SIGN *
* *
* *
* *
* *
* -------- *
* - - *
* - - *
* upper case or lower case -------- *
* *
***************************************************************************
Now type X to exit the CDrawApp program.
Example Summary
The CDrawApp program illustrates the use of a simple
interface. The CGTextEdit interface, used in this example,
provides a common set of access methods to three classes on different
branches of the CDrawApp class hierarchy, as shown in
Figure 6.2.
Figure 6.2 : The
Wyszukiwarka
Podobne podstrony:
ch6 (11)CH6ch6 (5)CH6ch6 (10)Beginning smartphone?velopment CH6CH6 (3)ch6 (8)Cisco2 ch6 Focusch6 (14)ch6 (9)ch6Cisco2 ch6 Vocab0472113038 ch6ch6 (12)ch6 (4)więcej podobnych podstron