• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Tim Cooke
  • Liutauras Vilda
  • Jeanne Boyarsky
  • paul wheaton
Sheriffs:
  • Ron McLeod
  • Devaka Cooray
  • Henry Wong
Saloon Keepers:
  • Tim Holloway
  • Stephan van Hulst
  • Carey Brown
  • Tim Moores
  • Mikalai Zaikin
Bartenders:
  • Frits Walraven

RMI confusion

 
Ranch Hand
Posts: 67
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I tired to look thru the previous posts to come to a conclusion about my case. Sorry i couldnt find an answer and i had to ask you people by opening a new thread.
Let me explain my layout first
DBAccess (Interface provided by SUN), implementing class - Data.java
I am using the ConnectionFactory solution, as discussed lots of times on this forum.
ConnectionInterface.java (self defined)
DataAdaptor.java (implements ConnectionInterface.java and holds a private instance of Data.java) - An instance of this is used for Local connections.
on the RMI side
RemoteConnectionInterface.java (extends ConnectionInterface and Remote)
RemoteConnectionInterfaceImpl.java (is the implementation of the above interface), and i am registering this Impl class with the registry.
This implementation class holds a private instance of the DataAdaptor, which inturn holds a private instance of Data.java.
I read the following solution in this forum, and thought of using the WeakHashMap (a static map in Data.java), where the key will be Data.this and the value against it will be the recNo. So i can identify which client locked which record.
This is only possible when each client connecting to the server(RMI), gets a unique instance of Data.java for the clients lifetime. But somehow in my case all the clients seem to get the same Data.java instance. (I just printed the hashCode of the Data.java instance. I checked it with 2 computers. One running the server & client(network mode). The other just the client connecting to the remote server).
Am i doing something wrong?
 
Ranch Hand
Posts: 18944
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Arun,
I don't think it would be a good idea for you to distinguish your clients on the Data class itself. There should only be one Data class per database file for all clients. Meaning all your clients should share one Data class. What you want is a wrapper around the Data class that is unique for each clients.
HTH.
-Amish
[ September 23, 2003: Message edited by: Amish Patel ]
 
Ranch Hand
Posts: 493
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hello Arun,
You have made great strides. I think I know where your problem might be. You wrote:


on the RMI side
RemoteConnectionInterface.java (extends ConnectionInterface and Remote)
RemoteConnectionInterfaceImpl.java (is the implementation of the above interface), and i am registering this Impl class with the registry.
This implementation class holds a private instance of the DataAdaptor, which inturn holds a private instance of Data.java.


You are not using connection factory pattern correctly. Your RemoteConnectionInterfaceImpl.java is the implementation of connection factory interface which you call "RemoteConnectionInterface.java". This implementing class must return another Remote Object (I called it DataRemoteImpl, you can call it whatever you want, but I will continue to refer to it DataRemoteImpl for the sake of explanation). Your RemoteConnectionInterfaceImpl.java class must have a method which you can call "create", "createconnection" or something. This method will return an instance of DataRemoteImpl. Another thing to keep in mind is that it is the DataRemoteImpl which must implement RemoteConnectionInterface.java not the RemoteConnectionInterfaceImpl.java. DataRemoteImpl will in turn, instantiate an instance of DataAdapter class.
Keep this in mind.
1. You create a connection factory interface (call it ConnectionFactory what else), it should extend Remote.
2. You create an implementing class, e.g., ConnectionFactoryImpl which will contain only one method (create or some such thing) for the express purpose of returning another remote object which I called DataRemoteImpl above. This class will implement your RemoteConnectionInterface above and instatiate the DataAdapter interface.
3. You register ConnectionFactoryImpl with RMI and not DataRemoteImpl.
Follow these steps and you should be fine. By the way, Andrew had posted a link to Sun's RMI tutorial which is excellent. Going through it, I could finally understand what the ConnectionFactory pattern in RMI was all about. I am posting it here for your convenience.
http://developer.java.sun.com/developer/onlineTraining/rmi/RMI.html
Good Luck.
Bharat
 
author and jackaroo
Posts: 12200
280
Mac IntelliJ IDE Firefox Browser Oracle C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi Amish,

I don't think it would be a good idea for you to distinguish your clients on the Data class itself. There should only be one Data class per database file for all clients. Meaning all your clients should share one Data class. What you want is a wrapper around the Data class that is unique for each clients.


I think it depends on the assignment. There are some assignments where having a unique Data class for each connected client makes perfect sense.
Regards, Andrew
 
Bharat Ruparel
Ranch Hand
Posts: 493
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hey Arun!
I am glad Andrew interjected here! I have URLy Bird 1.3.1 and for my assignment, as Andrew is pointing out, it makes perfect sense to have a unique instance of the Data class for each connected client. I am not sure which assignment do you have and what your instructions are. Please check carefully. I don't want to mislead you.
Thanks Andrew.
Regards.
Bharat
 
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
the link mentioned above is a very good link.
anyway, to make it easier for you, Factory pattern within RMI is:
ConnecitonInterface DataRemoteInt
| |
ConnectionInterfaceImplementation DataRemoteIntImplementation
ConnectionInterfaceImplementation has a reference to DataRemoteIntImplementation
that's it
 
Arun Kumar
Ranch Hand
Posts: 67
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thank you guys,
Let me post my interface ginve by SUN and my rational for choosing a unique Data instance for each client, Mine is the contractor assignment (v 2.2.2)
package suncertify.db;
public interface DBAccess
{
// Reads a record from the file. Returns an array where each
// element is a record value.
public String [] readRecord(long recNo)
throws RecordNotFoundException;
// Modifies the fields of a record. The new value for field n
// appears in data[n]. Throws SecurityException
// if the record is locked with a cookie other than lockCookie.
public void updateRecord(long recNo, String[] data, long lockCookie)
throws RecordNotFoundException, SecurityException;
// Deletes a record, making the record number and associated disk
// storage available for reuse.
// Throws SecurityException if the record is locked with a cookie
// other than lockCookie.
public void deleteRecord(long recNo, long lockCookie)
throws RecordNotFoundException, SecurityException;
// Returns an array of record numbers that match the specified
// criteria. Field n in the database file is described by
// criteria[n]. A null value in criteria[n] matches any field
// value. A non-null value in criteria[n] matches any field
// value that begins with criteria[n]. (For example, "Fred"
// matches "Fred" or "Freddy".)
public long[] findByCriteria(String[] criteria);
// Creates a new record in the database (possibly reusing a
// deleted entry). Inserts the given data, and returns the record
// number of the new record.
public long createRecord(String [] data)
throws DuplicateKeyException;
// Locks a record so that it can only be updated or deleted by this client.
// Returned value is a cookie that must be used when the record is unlocked,
// updated, or deleted. If the specified record is already locked by a different
// client, the current thread gives up the CPU and consumes no CPU cycles until
// the record is unlocked.
public long lockRecord(long recNo)
throws RecordNotFoundException;
// Releases the lock on a record. Cookie must be the cookie
// returned when the record was locked; otherwise throws SecurityException.
public void unlock(long recNo, long cookie)
throws SecurityException;
}
My class implementing this interface is Data.java.
When i want to book a contractor, from the GUIcontroller (same purpose as the example in Maxs book), i just call a method bookContractor(recNo); in the DataAdaptor.java (regardless of wether the connection is remote or local). So the guicontroller (or the client in this case) doesnt bother calling lock(), read(), updateRecord(), unlock() in that order. The DataAdaptors' bookContractor(recNo), internally calls these sequence of methods. So my thought is that it saves lots of data transfer back and forth between the client and the server. Because i am adopting this method (of sheilding lock, unlock from the guicontroller), the guicontroller doesnt have any knowledge of the random cookie it will be assigned when locking a record. The lock/unlock in my case is all handled on the server side. The client just receives a nice GUIException with a user friendly message wether his call to update was successful or not and why.
That said, if i dont choose individual Data instance, i wont know which client locked which record, and when i want to call the updateRecord() method i wont know which lockCookie i need to pass. If i use individual Data instance, i can use Data.this as the lockCookie, and i can store the recNo this instance has locked in a WeakHashMap.
Again, i can use a single Data instance, and make some stuff simple (like synchronizing all the mthods and avoid dirty read, dirty creation), but i have to call the sequence of methods lock, read, update, unlock from the GUIController, so the client will have the lockCookie in case of successful lock, and only one client can work with the database file at a time.
Do i make any sense atall
 
Bartender
Posts: 1872
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi Arun,

Do i make any sense atall


Well, from "When" to "why." it was perfect.

That said, if i dont choose individual Data instance, i wont know which client locked which record, and when i want to call the updateRecord() method i wont know which lockCookie i need to pass. If i use individual Data instance, i can use Data.this as the lockCookie, and i can store the recNo this instance has locked in a WeakHashMap.


Lock cookies are returned by lockRecord(), probably generated as random long values. Data.this is not a long cookie ! Unfortunately, I think that mapping Data instances to recNos in a static WeakHashMap is a solution which does not apply to assignments where cookies are used in lock() (as mine BTW). Now, I am just thinking to another possible solution : if you map cookies to recNos is a static WeakHashMap (where keys are cookies as Longs and values are recNos as Longs) , and if your Data instance (one per client) keeps the cookies it received in some HashMap as Longs, you should get the same protection against deadlocks caused by crashed clients. BTW, don't forget the notification issue of the WeakHashMap solution. I discussed it here recently.
Best,
Phil.
 
Arun Kumar
Ranch Hand
Posts: 67
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Philippe Maquet:
Hi Arun,

Lock cookies are returned by lockRecord(), probably generated as random long values. Data.this is not a long cookie ! Unfortunately, I think that mapping Data instances to recNos in a static WeakHashMap is a solution which does not apply to assignments where cookies are used in lock() (as mine BTW). Now, I am just thinking to another possible solution : if you map cookies to recNos is a static WeakHashMap (where keys are cookies as Longs and values are recNos as Longs) , and if your Data instance (one per client) keeps the cookies it received in some HashMap as Longs, you should get the same protection against deadlocks caused by crashed clients. BTW, don't forget the notification issue of the WeakHashMap solution. I discussed it here recently.
Best,
Phil.


Hi Phil,
I understand what you are saying. Ok, i cant use Data.this to map against the locked records (Cos my interface also uses long cookies to lock).
Your solution is something like the Data class has a static WeakHashMap which will have the unique cookies and the recNo locked against it.
And every data instace (one per client) will have individual HashMaps, where after each successful lock on recNo (successful entry into the static WeakHashMap), the data instance receives the long cookie and adds it into its own HaskMap against the record number it successuly locked. When unlocking, after a successful unlock (entry removed from the static WeakHashMap) the corresponding entry in its own HashMap will also be removed.
Am i on track?
I am still struggling to get individual data instances over RMI... working on it.....
 
Philippe Maquet
Bartender
Posts: 1872
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi Arun,

Am i on track?


Perfectly on track, and your english is far more understandable than mine...
Just notice that it's not my solution (I implemented a quite (too much ?) complex LockManager solution). It's just a way to adapt the classical WeakHashMap solution (which comes from Max) to assignments which use lock cookies.
Best,
Phil.
[ September 24, 2003: Message edited by: Philippe Maquet ]
 
Arun Kumar
Ranch Hand
Posts: 67
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hello Bharat,
As you might have seen, i am not using Data.this for my long cookie. See the discussion above with Phil.
But regarding creating individual data instance for each client, i am following your method. I am getting individual instance for each client, but it continues working even after i shut down the server
Any idea where it might have gone wrong?
Thankyou
 
Philippe Maquet
Bartender
Posts: 1872
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi Arun,
You just invented a kind of very tough, strong client ...
Best,
Phil.
[ September 24, 2003: Message edited by: Philippe Maquet ]
 
Arun Kumar
Ranch Hand
Posts: 67
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi Phil and Bharat,
Ok i got it working now It throws ConnectIOException when the server is down.
Ofcourse the connectioexception, i will wrap it a GUIControllerexception and have a nice dialog popup.
Got different data instance and takes care of connection problems.
Thanks ppl
 
Anonymous
Ranch Hand
Posts: 18944
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thanks Andrew for clearing that out. Or else I would have let the poster in the wrong direction. From the post it looked like that he was working on FBN solution.
-Amish

Originally posted by Andrew Monkhouse:
Hi Amish,

I think it depends on the assignment. There are some assignments where having a unique Data class for each connected client makes perfect sense.
Regards, Andrew

 
Bharat Ruparel
Ranch Hand
Posts: 493
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hello Arun,
Sorry I was busy. In the meantime, you were in very capable hands (Phil's) who has pretty much walked you through your design issues. He wrote:


Lock cookies are returned by lockRecord(), probably generated as random long values. Data.this is not a long cookie ! Unfortunately, I think that mapping Data instances to recNos in a static WeakHashMap is a solution which does not apply to assignments where cookies are used in lock() (as mine BTW). Now, I am just thinking to another possible solution : if you map cookies to recNos is a static WeakHashMap (where keys are cookies as Longs and values are recNos as Longs) , and if your Data instance (one per client) keeps the cookies it received in some HashMap as Longs, you should get the same protection against deadlocks caused by crashed clients. BTW, don't forget the notification issue of the WeakHashMap solution. I discussed it here recently.


If I understand him correctly, it is a really good solution for those who have Cookies in their method signatures. Let me see of I understand you correctly Phil: you are saying that in the DataAdapter class which keeps an instance of the Data class to do all the work, you should keep an instance variable declared as Long. Now this instance variable should store the record number as a Long. Therefore, you should call each method requiring locking with the SAME value for the locking cookie and the record number? So consider the following requirement defined in DBMain interface:
// Releases the lock on a record. Cookie must be the cookie
// returned when the record was locked; otherwise throws SecurityException.
public void unlock(long recNo, long cookie)
throws SecurityException;
What is it that should be sent in to this call? This will be called from the DataAdapter class right? Remember that the WeakHashMap needs Objects not primitives! How will the WeakHashMap know that the reference that it has stored as a Long (not long) has disappeared in the case when the client crashes? This is my only confusion, otherwise, it is a great solution!
Regards.
Bharat
 
Philippe Maquet
Bartender
Posts: 1872
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi Bharat,

you are saying that in the DataAdapter class which keeps an instance of the Data class to do all the work, you should keep an instance variable declared as Long.


Not exactly that :
  • It can be in Data, as far as there is one Data instance per client.
  • As there is no reason here to limit us to one lock per client at a time, a collection of cookies is better than a single Long variable. I wrote HashMap, but HashSet is even better.


  • It would give something like this :

    How will the WeakHashMap know that the reference that it has stored as a Long (not long) has disappeared in the case when the client crashes?


    If a client crashes, its DataAdapter instance will be eligible for gc, as its Data instance, as its LockCookies HashSet, as its Long cookies. And the latter becoming weakly reachable, the corresponding entries in lockedRecords will be cleared.
    I insist on the notification issue, which is solved in the thread I mentioned in a previous post in this thread. Without solving it, that solution simply is buggy.
    Best,
    Phil.
     
    Bharat Ruparel
    Ranch Hand
    Posts: 493
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Hello Phil,
    Let me think through your last post. I will respond by tomorrow.
    Thanks.
    Bharat
     
    Arun Kumar
    Ranch Hand
    Posts: 67
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Hi Phil,
    I think the client, DataAdaptor (client of Data, not the GUI client), from where i call the lockRecord(recNo) method, i need a HashMap, so i can receive the long lockCookie and convert to Long and store it in the HashMap against the locked recNo. The HashMap is needed since you are allowing the guiclient to choose multiple records from the JTable. So when calling unlock from DataAdaptor, i will know which lockCookie to send back to unlock. After a successful unlock i have to remove the corresponding entry from this HashMap.
    In my case i dont allow multiple selections from JTable, so i will always allow only one lock per guiclient. So in the DataAdaptor class i can have two instance variables which will store the recNo and the returned lockCookie.
    So for a single lock, the HashSet lockCookies in your code, will always have only one object in it.
    In case of client crash the HashSet lockCookies, will be lost, and thereby the corresponding entry in the WeakHashMap is weakly referenced and will be removed.
    I still havent looked into the notification problem, i will defenitely go thru it and bug you if i dont understand your solution
     
    Philippe Maquet
    Bartender
    Posts: 1872
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Hi Arun,

    The HashMap is needed since you are allowing the guiclient to choose multiple records from the JTable. So when calling unlock from DataAdaptor, i will know which lockCookie to send back to unlock. After a successful unlock i have to remove the corresponding entry from this HashMap.


    I don't agree, because from your DataAdaptor, you'll need to unlock by a call to Data unlock() which requires the recNo and the cookie as parameters. So even if Data keeps the cookies for the purpose we discussed above, we don't need to track the cookies/recNo pair. That's why I proposed to use an HashSet instead.

    In my case i dont allow multiple selections from JTable, so i will always allow only one lock per guiclient. So in the DataAdaptor class i can have two instance variables which will store the recNo and the returned lockCookie.


    Good point (except that Long lockCookie is enough). But I personally don't share the design choice : I need to book only one room at a time (OK) so I don't need more that one lock at a time (OK) so I design a single-lock-per-client locking system (not OK IMO). Even if our instructions state that "the IT director does not anticipate much reuse of the first Java technology system", why limit the locking system that much ? The fact that you'll book one room at a time is a business decision (which may change in the future BTW). The fact that your locking system allows or not multiple locks per clients is a feature of your database system. I don't see any relation between both, except that your database system at least must be able to offer what your business tier needs. But better the more IMO.
    Best,
    Phil.
    [ September 24, 2003: Message edited by: Philippe Maquet ]
     
    Philippe Maquet
    Bartender
    Posts: 1872
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Hi Arun,
    I have one more argument against the single-lock-per-client limitation. If SUN had this limitation in mind, unlock() would not have a recNo as parameter.
    Best,
    Phil.
     
    Arun Kumar
    Ranch Hand
    Posts: 67
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Sorry Phil, i lost you there.
    Sorry for beating the dead horse again

    The caller of the above lock method (DataAdaptor) gets back the lockCookie (long value). So it can send it back while calling unlock method (below). But what will be the record number value passed to the unlock call, if you dont keep track of the recno, cookie pairs for your data instance somewhere. So while unlocking, the recNo, cookie you are sending back will be checked against the pair in the static WeakHashMap. If the pairs dont match then the securityException will be thrown
    So how does the HashSet help you with the above problem? And what is the purpose of the HashSet intended by you?

    [ September 25, 2003: Message edited by: Arun Kumar ]
     
    Philippe Maquet
    Bartender
    Posts: 1872
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Hi Arun,
    You have three levels : DataAdapter --> Data --> lockedRecords (static in Data).
  • The cookies/recNos key/value pairs are already stored in lockedRecords.
  • Data only needs to store cookies as Longs, for the only purpose of helping WeakHashMap lockedRecords to track crashed clients.
  • DataAdapter methods which call lock() need to keep the cookies/recNos pairs in order to be able to unlock the records they locked. Now, in DataAdapter, if you never lock more than one record at a time, of course just save the recNo and cookie in two simple long variables. Else, a HashMap could be OK or even two long arrays (if locks are handled locally as a whole within single methods).


  • Best,
    Phil.
     
    Arun Kumar
    Ranch Hand
    Posts: 67
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Hi Phil,

    Originally posted by Philippe Maquet:
    Hi Arun,
    You have three levels : DataAdapter --> Data --> lockedRecords (static in Data).

  • The cookies/recNos key/value pairs are already stored in lockedRecords.
  • Data only needs to store cookies as Longs, for the only purpose of helping WeakHashMap lockedRecords to track crashed clients.
  • DataAdapter methods which call lock() need to keep the cookies/recNos pairs in order to be able to unlock the records they locked. Now, in DataAdapter, if you never lock more than one record at a time, of course just save the recNo and cookie in two simple long variables. Else, a HashMap could be OK or even two long arrays (if locks are handled locally as a whole within single methods).


  • Best,
    Phil.


    This was exactly what i had in mind. I was clear about the first and the second point. I was only discussing about the 3rd point you mentioned above, in my previous post (about 4 posts above), babbling about HashMaps and stuff.
    So afterall my english aint good enuff as you had mentioned and i didnt come clean the first time, for you to understand....
    Thankyou Phil, it all makes perfect sense now, and i will continue to look at the notification issue you mentioned earlier.
    I still havent thought about dirty reads. Should i avoid reading locked records there by avoiding dirty reads?
    How about create records? If i am over writing an already deleted record, i have a record number which i can lock. How about if i had to create the record at the end of the file? May be having some kind of static member (I have a DBInit script which will run , before starting the RMI service, reading thru the header, initializing some static members with db header info) which will hold the number of records (numRecords), and the createRecord method can synchronize on numRecords. If the data has to be written to the end of the file, then get the lock on numRecords, increment it by one, gain a lock on this new recNumber, and write it to the end of the file, at the end unlock the recNo.
    I am not sure how well this might work, havent even given a deep thought on this idea. It just occured to me.
    BTW, i am not using any form of caching, my read and write will always go back to db file. I know reading the file everytime someone searches is not very nice, but i can live with it since caching is not a "must" according to the instructions.
    Arun
     
    Check your pockets for water buffalo. You might need to use this tiny ad until locate a water buffalo:
    Gift giving made easy with the permaculture playing cards
    https://coderanch.com/t/777758/Gift-giving-easy-permaculture-playing
    reply
      Bookmark Topic Watch Topic
    • New Topic